360 lines
13 KiB
Markdown
360 lines
13 KiB
Markdown
# API-libdatacenter — 数据中心模块
|
||
|
||
> 版本: v3.0
|
||
> 日期: 2026-07-07
|
||
> 状态: 已实现(对齐 RTU 原工程 v1+v2 全部功能)
|
||
|
||
---
|
||
|
||
## 1. 模块概述
|
||
|
||
libdatacenter 是 RTU 通讯装置的核心数据中间层,负责:
|
||
|
||
| 功能 | 说明 |
|
||
|------|------|
|
||
| 信号注册与管理 | 5 种信号类型(out/in/yk/ao/param)统一存储和 XXH3 哈希索引 |
|
||
| 回路阻断 | 通过 `module_id` 实现"修改信号的模块不收到自己的回调" |
|
||
| 延迟变化检测 | out 信号采用脏标记 + 100ms 批量检测,避免频繁回调 |
|
||
| SBO 控制流程 | 支持直控(Direct)和选控(Select-Before-Operate)两种模式 |
|
||
| 参数元数据 | min/max/step/unit 值校验 |
|
||
| 配置文件持久化 | 通过 libxml C 接口读写 param.xml/self_param.xml |
|
||
| 事件队列 | SOE 事件、扰动数据、故障录波三类队列(支持多消费者) |
|
||
| 异步执行 | yk/ao/param 异步控制 + 超时检测 + 取消 |
|
||
| 命令调试 | 通过 libcmd 注册 `datacenter` 命令,交互式查看信号 |
|
||
|
||
### 架构依赖
|
||
|
||
```
|
||
libmy_xxhash → libxml → libcmd
|
||
↘ ↙
|
||
libdatacenter
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 数据结构
|
||
|
||
### 2.1 控制类型
|
||
```c
|
||
typedef enum
|
||
{
|
||
DC_CTRL_NONE = 0, /**< 无控制 */
|
||
DC_CTRL_DIRECT = 1, /**< 直控(直接执行) */
|
||
DC_CTRL_SBO = 2 /**< 选控(Select-Before-Operate) */
|
||
} dc_ctrl_type_t;
|
||
```
|
||
|
||
### 2.2 控制步骤
|
||
```c
|
||
typedef enum
|
||
{
|
||
DC_STEP_READY = 0, /**< 就绪 */
|
||
DC_STEP_SELECT = 1, /**< 选择 */
|
||
DC_STEP_DIRECT = 2, /**< 执行 */
|
||
DC_STEP_CANCEL = 3 /**< 取消 */
|
||
} dc_ctrl_step_t;
|
||
```
|
||
|
||
### 2.3 SBO 状态转换
|
||
```
|
||
READY ──→ SELECT(锁住信号,暂存值到 ctrl.p_data)
|
||
│ │
|
||
│ ├──→ DIRECT(返校比对 → 写入 → ctrl.step = READY)
|
||
│ │
|
||
│ └──→ CANCEL(回退,ctrl.step = READY)
|
||
│
|
||
└──→ DIRECT(直控:直接写入)
|
||
```
|
||
|
||
### 2.4 控制上下文
|
||
```c
|
||
typedef struct
|
||
{
|
||
uint8_t step; /**< 当前步骤 */
|
||
uint8_t type; /**< 控制类型 */
|
||
uint8_t data_type; /**< 数据类型 */
|
||
uint8_t reserved; /**< 保留 */
|
||
void *p_data; /**< SELECT时暂存选择值,DIRECT时返校比对 */
|
||
} dc_ctrl_t;
|
||
```
|
||
|
||
### 2.5 参数元数据
|
||
```c
|
||
typedef struct
|
||
{
|
||
float min; /**< 最小值 */
|
||
float max; /**< 最大值 */
|
||
float step; /**< 步长 */
|
||
char unit[32]; /**< 单位 */
|
||
} dc_param_meta_t;
|
||
```
|
||
|
||
### 2.6 错误码
|
||
| 值 | 名称 | 说明 |
|
||
|----|------|------|
|
||
| 0 | DC_OK | 成功 |
|
||
| -1 | DC_ERR_PARAM | 参数错误 |
|
||
| -2 | DC_ERR_NOTFOUND | 信号不存在 |
|
||
| -3 | DC_ERR_TYPE | 数据类型不匹配 |
|
||
| -4 | DC_ERR_STEP | SBO 步骤错误 |
|
||
| -5 | DC_ERR_VAL | 值校验失败 |
|
||
| -6 | DC_ERR_TIMEOUT | 异步超时 |
|
||
| -7 | DC_ERR_BUSY | 忙 |
|
||
| -8 | DC_ERR_MEM | 内存不足 |
|
||
| -9 | DC_ERR_FULL | 队列已满 |
|
||
| -10 | DC_ERR_LOCKED | 已锁定 |
|
||
| -11 | DC_ERR_EXISTS | 重复注册 |
|
||
| -12 | DC_ERR_CANCEL | 已取消 |
|
||
|
||
---
|
||
|
||
## 3. 接口函数
|
||
|
||
### 3.1 模块生命周期
|
||
|
||
| 函数 | 说明 |
|
||
|------|------|
|
||
| `dc_init(cfg_dir)` | 初始化,自动加载 param.xml |
|
||
| `dc_cleanup()` | 清理所有资源 |
|
||
| `dc_run_100ms()` | 每100ms:out变化检测 + 事件弹出 + 异步超时 |
|
||
| `dc_run_1000ms()` | 每1000ms:参数变更检测 → 写 self_param.xml |
|
||
|
||
### 3.2 Out 信号(遥测/遥信)
|
||
|
||
| 函数 | 说明 |
|
||
|------|------|
|
||
| `dc_signal_out(saddr, desc, data_type, p_data, module_id)` | 注册输出信号 |
|
||
| `dc_signal_out_with_callback(..., cb, module_id)` | 注册 + 延迟变化回调 |
|
||
| `dc_signal_out_link_with_callback(saddr, pp_data, cb, module_id)` | 链接已有信号 |
|
||
| `dc_set_out_signal_val(saddr, set_data, module_id)` | 设值(脏标记 + 延迟回调) |
|
||
|
||
### 3.3 In 信号(输入信号,链接 out)
|
||
|
||
| 函数 | 说明 |
|
||
|------|------|
|
||
| `dc_signal_in(saddr, desc, link_saddr, pp_data, module_id)` | 注册并关联 out |
|
||
| `dc_signal_in_with_callback(..., cb, module_id)` | 注册 + 回调(挂在 out 上) |
|
||
|
||
### 3.4 YK 信号(遥控)
|
||
|
||
| 函数 | 说明 |
|
||
|------|------|
|
||
| `dc_signal_yk(...)` | 注册遥控信号 |
|
||
| `dc_signal_yk_link_with_callback(...)` | 链接已有 |
|
||
| `dc_signal_yk_set_status(saddr, step, ctrl, p_data, module_id)` | 同步控制 |
|
||
| `dc_yk_exec_async(saddr, step, ctrl, p_data, module_id, result_cb, arg, timeout, req_id)` | 异步控制 |
|
||
| `dc_yk_set_result(req_id, result, err)` | 异步结果通知 |
|
||
| `dc_yk_cancel_async(req_id)` | 取消异步 |
|
||
| `dc_yk_register_exec_cb(saddr, exec_cb, ctx)` | 注册执行回调 |
|
||
|
||
### 3.5 AO 信号(模拟输出)
|
||
|
||
| 函数 | 说明 |
|
||
|------|------|
|
||
| `dc_signal_ao(...)` | 注册 AO 信号 |
|
||
| `dc_signal_ao_link_with_callback(...)` | 链接已有 |
|
||
| `dc_signal_ao_set_val(saddr, step, ctrl, p_data, module_id)` | 同步控制(SBO + 值校验) |
|
||
| `dc_signal_ao_set_val_without_check(...)` | 无校验设值 |
|
||
| `dc_ao_exec_async(...)` | 异步控制 |
|
||
| `dc_ao_set_result(...)` | 异步结果通知 |
|
||
| `dc_ao_cancel_async(...)` | 取消异步 |
|
||
| `dc_ao_register_exec_cb(...)` | 注册执行回调 |
|
||
|
||
### 3.6 Param 信号(定值,多区)
|
||
|
||
| 函数 | 说明 |
|
||
|------|------|
|
||
| `dc_signal_param(saddr, desc, data_type, ctrl_type, pp_data, num_zones, cb, module_id)` | 注册参数 |
|
||
| `dc_signal_param_link_with_callback(...)` | 链接已有 |
|
||
| `dc_signal_param_set_val(saddr, step, ctrl, zone, p_data, module_id)` | 同步设值 |
|
||
| `dc_signal_param_set_val_without_check(...)` | 无校验设值 |
|
||
| `dc_param_exec_async(...)` | 异步控制 |
|
||
| `dc_param_set_result(...)` | 异步结果通知 |
|
||
| `dc_param_cancel_async(...)` | 取消异步 |
|
||
| `dc_param_register_exec_cb(...)` | 注册执行回调 |
|
||
|
||
### 3.7 信号查询
|
||
|
||
| 函数 | 说明 |
|
||
|------|------|
|
||
| `dc_get_out_signal_info(saddr, desc, desc_len, data_type, pp_data)` | 查询 out |
|
||
| `dc_get_in_signal_info(...)` | 查询 in |
|
||
| `dc_get_ao_signal_info(saddr, desc, desc_len, data_type, p_meta, ctrl_type, pp_data)` | 查询 AO(含元数据) |
|
||
| `dc_get_param_signal_info(saddr, desc, desc_len, data_type, p_meta, ctrl_type, p_data_array, p_count)` | 查询 Param(多区) |
|
||
| `dc_get_yk_signal_info(...)` | 查询 YK |
|
||
| `dc_get_signal_val(p_data, data_type, buf, buf_len)` | 值 → 字符串 |
|
||
| `dc_get_signal_count(type_str)` | 按类型统计 |
|
||
| `dc_get_signal_info_by_id(type_str, id, ...)` | 按 ID 查询 |
|
||
|
||
### 3.8 事件队列
|
||
|
||
| 函数 | 说明 |
|
||
|------|------|
|
||
| `dc_event_queue_push(p_soe)` | SOE 入队 |
|
||
| `dc_event_register_queue_pop(cb)` | 注册 SOE 消费回调 |
|
||
| `dc_disturb_dd_queue_push(p_dd)` | 扰动数据入队 |
|
||
| `dc_disturb_dd_register_queue_pop(cb)` | 注册扰动消费回调 |
|
||
| `dc_fault_queue_push(p_fault)` | 故障数据入队 |
|
||
| `dc_fault_register_queue_pop(cb)` | 注册故障消费回调 |
|
||
|
||
### 3.9 类型工具
|
||
|
||
| 函数 | 说明 |
|
||
|------|------|
|
||
| `dc_get_type_name(data_type)` | ID → 名称(35 → "uint32_t") |
|
||
| `dc_get_type_id_by_name(name)` | 名称 → ID("float" → 38) |
|
||
| `dc_get_type_len(data_type)` | 获取字节长度 |
|
||
| `dc_create_data(data_type)` / `dc_destroy_data(p_data, data_type)` | 类型感知的分配/释放 |
|
||
| `dc_copy_val(dst, src, data_type)` / `dc_compare_val(p1, p2, data_type)` | 类型感知的复制/比较 |
|
||
| `dc_set_val_from_str(p_data, data_type, str)` | 字符串 → 值 |
|
||
|
||
### 3.10 配置与异步管理
|
||
|
||
| 函数 | 说明 |
|
||
|------|------|
|
||
| `dc_param_cfg_changed()` | 查询是否有未保存变更 |
|
||
| `dc_async_pending_count()` | 异步请求 pending 数 |
|
||
| `dc_async_set_default_timeout(ms)` | 全局默认超时 |
|
||
| `dc_async_set_timeout_yk/ao/param(ms)` | 各类型默认超时 |
|
||
| `dc_save_out_signals_xml(path)` | 导出 out 信号到 XML |
|
||
|
||
---
|
||
|
||
## 4. 使用示例
|
||
|
||
```c
|
||
#include "myDatacenter.h"
|
||
#include <stdio.h>
|
||
|
||
static void on_out_changed(const char *saddr, uint8_t data_type,
|
||
const void *p_new, const void *p_old)
|
||
{
|
||
printf("out %s: %d → %d\n", saddr, *(int *)p_old, *(int *)p_new);
|
||
}
|
||
|
||
static void on_yk_changed(const char *saddr, dc_ctrl_step_t step,
|
||
uint8_t data_type, uint8_t zone,
|
||
const void *p_data)
|
||
{
|
||
printf("yk %s step=%d val=%d\n", saddr, step, *(uint8_t *)p_data);
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
/* 初始化,自动加载 param.xml */
|
||
dc_init("/etc/rtu/config/");
|
||
|
||
/* 注册 out 信号 */
|
||
int yc_val = 0;
|
||
dc_signal_out("RPC1:YC:1:val", "母线电压", DATA_TYPE_S32,
|
||
&yc_val, "protocol_104");
|
||
|
||
/* 另一个模块链接 out 信号并注册回调 */
|
||
void *p_linked = NULL;
|
||
dc_signal_out_link_with_callback("RPC1:YC:1:val", &p_linked,
|
||
on_out_changed, "web_server");
|
||
|
||
/* 注册 yk 信号(直控模式) */
|
||
uint8_t yk_val = 0;
|
||
dc_signal_yk("RPC1:YK:1:val", "开关合闸", DATA_TYPE_U8,
|
||
DC_CTRL_DIRECT, &yk_val, on_yk_changed, "protocol_104");
|
||
|
||
/* 修改 out 信号值(回路阻断:protocol_104 自身的回调不会被触发) */
|
||
yc_val = 220;
|
||
dc_set_out_signal_val("RPC1:YC:1:val", &yc_val, "protocol_104");
|
||
|
||
/* 100ms 周期:on_out_changed 被调用 */
|
||
dc_run_100ms();
|
||
|
||
/* YK 直控执行(同步回调:on_yk_changed 立即触发) */
|
||
dc_ctrl_t ctrl = {0};
|
||
ctrl.type = DC_CTRL_DIRECT;
|
||
ctrl.data_type = DATA_TYPE_U8;
|
||
uint8_t new_val = 1;
|
||
dc_signal_yk_set_status("RPC1:YK:1:val", DC_STEP_DIRECT,
|
||
&ctrl, &new_val, "web_server");
|
||
|
||
dc_cleanup();
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 依赖关系
|
||
|
||
| 依赖 | 模块 | 用途 |
|
||
|------|------|------|
|
||
| ⇩ | `myBase.h` | 基础类型定义(DATA_TYPE_* 宏) |
|
||
| ⇩ | `myLog.h` | 日志输出 |
|
||
| ⇩ | `libmy_xxhash` | XXH3_128bits 哈希查找(O(1)) |
|
||
| ⇩ | `libxml` | 纯 C 接口 XML 解析(param.xml/self_param.xml) |
|
||
| ⇩ | `libcmd` | 命令注册(`datacenter` 调试面板) |
|
||
|
||
---
|
||
|
||
## 6. 源文件组成(15 文件,对齐 RTU 原工程)
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `inc/myDatacenter.h` | 纯 C 对外接口(60+ API) |
|
||
| `src/dc_internal.h` | C++ 内部声明(哈希/信号表/异步队列) |
|
||
| `src/dc_core.cpp` | 全局变量 + XXH128 哈希 + dc_init/run |
|
||
| `src/dc_signal_out.cpp` | Out 信号注册/链接/设值 |
|
||
| `src/dc_signal_in.cpp` | In 信号注册(关联 out) |
|
||
| `src/dc_signal_yk.cpp` | YK 注册 + 同步/异步控制 |
|
||
| `src/dc_signal_ao.cpp` | AO 注册 + 同步/异步控制 + SBO |
|
||
| `src/dc_signal_param.cpp` | Param 多区定值 + 同步/异步控制 |
|
||
| `src/dc_signal_query.cpp` | 全部 get_xxx_info 函数 |
|
||
| `src/dc_signal_util.cpp` | 19 种类型工具函数 |
|
||
| `src/dc_signal_check.cpp` | SBO 校验 + 值校验 + YK 校验 |
|
||
| `src/dc_signal_show.cpp` | 命令注册 + XML 导出 |
|
||
| `src/dc_param_cfg.cpp` | param.xml 解析 + self_param.xml 保存 |
|
||
| `src/dc_event.cpp` | SOE/扰动/故障事件队列 |
|
||
| `src/dc_async.cpp` | 异步请求队列(入队/超时/取消/清理) |
|
||
| `src/dc_api_wrap.cpp` | C API 包装(get_type_name 等) |
|
||
|
||
---
|
||
|
||
## 7. 新旧工程差异
|
||
|
||
| 维度 | RTU v1 | RTU v2 | 新工程 |
|
||
|------|--------|--------|--------|
|
||
| 接口语言 | C++ (std::string) | 纯 C (const char*) | 纯 C + Allman 风格 |
|
||
| 哈希 | XXH128 multimap | 同 v1 | unordered_map + XXH128 hasher |
|
||
| 参数文件 | tinyxml2 嵌入 | 同 v1 | libxml C 接口 |
|
||
| 异步执行 | 无 | _exec_async 系列 | _exec_async 系列 |
|
||
| 回路阻断 | ✓ | ✓ | ✓ |
|
||
| SBO 流程 | ✓ | ✓ | ✓ |
|
||
| 多区定值 | ✓ | ✓ | ✓ |
|
||
| 错误码 | -1/0 | enum dc_error_t | enum dc_error_t(12种) |
|
||
| 命令调试 | cmd_dc | 无 | CMD_REGISTER_C(libcmd) |
|
||
| 文件数 | 13 .cpp | 8 .cpp | 15 .cpp(按功能拆分) |
|
||
|
||
### 7.1 版本历史
|
||
|
||
| 日期 | 版本 | 变更 |
|
||
|------|------|------|
|
||
| 2026-07-07 | v3.0 | 初始实现,对齐 RTU 原工程 v1+v2 |
|
||
| 2026-07-10 | v3.1 | Bug 修复 + 原工程功能补全(详见下表) |
|
||
|
||
### v3.1 修复明细(2026-07-10)
|
||
|
||
| # | 文件 | 变更 | 说明 |
|
||
|:--|------|------|------|
|
||
| 1 | `dc_async.cpp` | 🔧 死锁修复 | `timeout_check()` 持锁调回调 → 先收集列表,解锁后批量调用 |
|
||
| 2 | `dc_api_wrap.cpp` | 🔧 线程安全 | `dc_get_type_name()` `static string` → `thread_local char[32]` |
|
||
| 3 | `dc_event.cpp` | 🔧 队列防溢 | SOE/扰动/故障队列无上限 → `EVENT_QUEUE_MAX_SIZE=1024` |
|
||
| 4 | `dc_signal_out.cpp` | 🔧 回路阻断竞态 | `last_caller_module` 多模块覆盖 → 新增 `dirty_by_multi` 标志 |
|
||
| 5 | `dc_signal_check.cpp` | ➕ 值范围校验 | `dc_check_val_valid` 扩展为 12 种数值类型 min/max 范围 + bool 0/1 校验 |
|
||
| 6 | `dc_signal_check.cpp` | ➕ strcmp 替代 | `dc_check_ctrl_val_valid` 对 C8/C32/C64/C128/STR 改用 `strcmp` |
|
||
| 7 | `dc_signal_util.cpp` | ➕ 线程安全 API | 新增 `dc_internal_type_name()` buffer 版类型名称查询 |
|
||
|
||
### 已知未完成
|
||
|
||
| 功能 | 优先级 | 说明 |
|
||
|------|:----:|------|
|
||
| 事件队列自动补时间戳 | 🟡 | push 时 `sec==0 && ms==0` 自动获取当前时间 |
|
||
| `dc_param_cfg_register_read_cb/save_cb` 钩子 | 🟡 | SPEC D29-D30,当前直接内置 tinyxml2 |
|
||
| `vec_p_default_data` | 🟢 | param 信号出厂默认值 |
|