RTU_ALL_AI/SPEC.md

761 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# RTU_ALL_AI 模块功能规范(基于 RTU 原工程分析)
> 生成时间: 2026-07-07
> 原则: 每条功能点均来自 RTU 原工程对应模块的完整分析,不遗漏不编造
---
## 1. myBase.h
### 功能点
| 序号 | 功能 | 说明 |
|------|------|------|
| B01 | 颜色宏 | COLOR_RESET/RED/GREEN/YELLOW/BLUE/MAGENTA/CYAN/WHITE |
| B02 | 文件名缩写宏 | `__SHORT_FILE__` 去除路径前缀 |
| B03 | 基础日志宏 | `MY_LOG(color,level,fmt,...)` / `MY_LOG_I` / `MY_LOG_E` |
| B04 | LOCAL 宏 | `#define LOCAL static` |
| B05 | 字符串类型 | `S_STR[64]` / `L_STR[128]` |
| B06 | uint16 大小端转换 | `MY_GET/SET_SHORT_BIGEND/LITTLEEND` |
| B07 | int32 大小端转换 | `MY_GET/SET_INT_BIGEND/LITTLEEND` |
| B08 | uint64 大小端转换 | `MY_GET/SET_LONG_BIGEND/LITTLEEND` |
| B09 | 类型级存/取宏 | `MY_GET/SET_DATA_WITH_TYPE` |
| B10 | 数据类型常量 | DATA_TYPE_B/S8/U8/.../STR (22种) |
---
## 2. liblist 侵入式链表
### 功能点Linux 内核标准 list.h 精简版)
| 序号 | 功能 | API |
|------|------|------|
| L01 | 链表初始化 | `LIST_HEAD` / `INIT_LIST_HEAD` |
| L02 | 头插 | `list_add` |
| L03 | 尾插 | `list_add_tail` |
| L04 | 删除 | `list_del` / `list_del_init` |
| L05 | 移动 | `list_move` / `list_move_tail` |
| L06 | 替换 | `list_replace` / `list_replace_init` |
| L07 | 判空 | `list_empty` / `list_is_linked` |
| L08 | 节点→宿主转换 | `list_entry` |
| L09 | 取首/尾元素 | `list_first_entry` / `list_last_entry` / `list_first_entry_or_null` |
| L10 | 遍历(安全) | `list_for_each` / `list_for_each_entry` / `*_safe` |
| L11 | 拼接 | `list_splice_init` |
### 关键约束
- 纯头文件,无运行时依赖
- 所有 API 必须兼容 C 和 C++(变量名不用 `new`/`class``typeof`→`__typeof__`
---
## 3. liblog 异步日志系统
### 功能点636 行,完整复刻 RTU 原实现)
| 序号 | 功能 | 说明 |
|------|------|------|
| G01 | 异步日志线程 | `log_worker_thread` 独立线程处理日志写入 |
| G02 | 懒初始化 | `pthread_once` 实现首次调用自动初始化 |
| G03 | 消息池预分配 | `calloc(LOG_QUEUE_SIZE)` 预分配消息池,避免频繁 malloc/free |
| G04 | 消息池取还 | `get_message_from_pool` / `return_message_to_pool` |
| G05 | 队列满阻塞 | `pthread_cond_wait` 等待 not_full不丢弃日志 |
| G06 | 批量消费 | 一次最多取 `LOG_BATCH_SIZE`=100 条,批量写入 |
| G07 | 控制台输出 | 带颜色 ANSI 码输出 |
| G08 | 文件输出 | 带时间戳写入日志文件 |
| G09 | 文件自动创建 | `init_log_directory` 自动创建 logs/ 目录 |
| G10 | 文件按大小轮转 | 超过 10MB 自动轮转,保留 5 个历史文件 |
| G11 | 文件 setlinebuf | 行缓冲模式 |
| G12 | 时间戳格式化 | 精确到毫秒 `YYYY-MM-DD HH:MM:SS.mmm` |
| G13 | 文件信息格式化 | `文件名/函数名:行号` 格式 |
| G14 | LOG_I/LOG_E 宏 | 便捷宏 |
| G15 | 开关控制 | `log_set_file_enabled` / `log_set_console_enabled` |
| G16 | flush | 等待队列清空 |
| G17 | cleanup | 停止线程、关闭文件、释放消息池 |
### 程序运行流程
```
首次 log_prt() 调用
→ pthread_once → log_global_init
→ init_message_pool (预分配 LOG_QUEUE_SIZE 个消息结构)
→ logger.running = 1
→ pthread_create(&log_worker_thread)
每次 log_prt()
→ get_message_from_pool()
→ 填充 level/timestamp/file_info/content
→ enqueue_log()
→ 队列满则 pthread_cond_wait(&not_full) 阻塞等待
→ 尾插队列
→ pthread_cond_signal(&not_empty)
log_worker_thread 主循环
→ 批量取消息 (最多 LOG_BATCH_SIZE)
→ write_log_batch_array()
→ 控制台: printf 带颜色
→ 文件: fopen/fprintf, 超过 10MB 触发 rotate_log_files
→ return_message_to_pool() 归还每个消息
```
---
## 4. libfunc 基础工具函数
### 功能点1783 行,完整复刻 RTU 原实现)
#### 4.1 校验计算 (6 个函数)
| 序号 | 函数 | 说明 |
|------|------|------|
| F01 | `func_cal_sum_byte` | 8位校验和 |
| F02 | `func_cal_sum_word` | 16位校验和 |
| F03 | `func_cal_crc16` | CRC16(Modbus 多项式) |
| F04 | `func_cal_crc32` | CRC32(标准多项式) |
| F05 | `func_cal_file_crc16` | 文件 CRC16 (4K 缓冲区) |
| F06 | `func_cal_file_crc32` | 文件 CRC32 (4K 缓冲区) |
#### 4.2 字符串转换 (10 个函数)
| 序号 | 函数 | 说明 |
|------|------|------|
| F07 | `func_get_hex_from_ASCLL` | 单字符hex→数值 |
| F08 | `func_get_dec_from_ASCLL` | 单字符dec→数值 |
| F09 | `func_string_hex_to_dec` | hex字符串→int |
| F10 | `func_string_dec_to_dec` | dec字符串→int |
| F11 | `func_string_hex_to_buf` | hex字符串→字节缓冲 |
| F12 | `func_dec_to_string` | int→dec字符串(支持宽度填充) |
| F13 | `func_hex_to_string` | int→hex字符串(支持宽度填充) |
| F14 | `func_float_to_string` | float→字符串(支持宽度) |
| F15 | `func_buf_to_string` | 字节缓冲→hex字符串 |
| F16 | `func_string_to_argv` | 字符串→argc/argv分割 |
#### 4.3 时间处理 (6 个函数)
| 序号 | 函数 | 说明 |
|------|------|------|
| F17 | `func_sys_time_to_string` | 系统时间结构体→字符串 |
| F18 | `func_string_to_sys_time` | 字符串→系统时间结构体 |
| F19 | `func_get_time` | 获取系统时间(支持 sys/cal 两种) |
| F20 | `func_set_time` | 设置系统时间 |
| F21 | `func_time_sys_to_cal` | 系统时间→日历时间 |
| F22 | `func_time_cal_to_sys` | 日历时间→系统时间 |
#### 4.4 文件目录操作 (11 个函数)
| 序号 | 函数 | 说明 |
|------|------|------|
| F23 | `func_dir_exist` | 目录是否存在 |
| F24 | `func_make_dirs` | 创建目录(递归) |
| F25 | `func_del_dirs` | 删除目录(递归) |
| F26 | `func_del_dirs_by_cmd` | 删除目录(系统命令) |
| F27 | `func_file_exist` | 文件是否存在 |
| F28 | `func_get_file_size` | 获取文件大小 |
| F29 | `func_read_file_to_buf` | 读文件到预分配buf |
| F30 | `func_read_file_alloc_buf` | 读文件到动态分配buf |
| F31 | `func_write_buf_to_file` | 写buf到文件 |
| F32 | `func_read_file_tip_info` | 读结构体格式文件信息(含CRC校验) |
| F33 | `func_make_dir`(内部) | 内部单级目录创建 |
#### 4.5 进程管理 (9 个函数)
| 序号 | 函数 | 说明 |
|------|------|------|
| F34 | `func_get_process_self_name` | 获取本进程名 |
| F35 | `func_get_process_name_by_pid` | PID→进程名 |
| F36 | `func_get_process_pid` | 获取本进程PID |
| F37 | `func_get_process_pid_by_name` | 进程名→PID |
| F38 | `func_process_exist_by_name` | 按名判断进程存在 |
| F39 | `func_process_exist_by_pid` | 按PID判断进程存在 |
| F40 | `func_get_process_path_by_pid` | PID→进程路径 |
| F41 | `func_get_process_self_path` | 本进程路径 |
| F42 | `func_get_process_self_dir` | 本进程目录 |
#### 4.6 环境变量路径 (12 个函数)
| 序号 | 函数 | 说明 |
|------|------|------|
| F43 | `func_get_env_value`(内部) | 读环境变量 |
| F44 | `func_get_env_path`(内部) | 读环境变量路径 |
| F45 | `func_get_work_path` | WORK_PATH |
| F46 | `func_get_sys_cfg_path` | SYS_CFG_PATH |
| F47 | `func_get_app_root_path` | APP_ROOT_PATH |
| F48 | `func_get_his_root_path` | HIS_ROOT_PATH |
| F49 | `func_get_log_root_path` | LOG_ROOT_PATH |
| F50 | `func_get_dbc_root_path` | DBC_ROOT_PATH |
| F51 | `func_get_shell_path` | SHELL_PATH |
| F52 | `func_get_update_path` | UPDATE_PATH |
| F53 | `func_get_app_path` | 应用路径(拼接后) |
| F54 | `func_get_app_cfg_path` | 应用配置路径 |
| F55 | `func_get_app_his_path` | 应用历史路径 |
| F56 | `func_get_app_log_path` | 应用日志路径 |
#### 4.7 IPC 消息队列 (9 个函数)
| 序号 | 函数 | 说明 |
|------|------|------|
| F57 | `func_create_ipc_msg_queue_by_name` | 按名称创建 |
| F58 | `func_create_ipc_msg_queue_by_pid` | 按PID创建 |
| F59 | `func_get_ipc_msg_queue_by_name` | 按名称获取 |
| F60 | `func_get_ipc_msg_queue_by_pid` | 按PID获取 |
| F61 | `func_ipc_msg_send_buf` | 发送原始buf |
| F62 | `func_ipc_msg_recv_buf` | 接收原始buf |
| F63 | `func_ipc_msg_send_struct` | 发送结构体 |
| F64 | `func_ipc_msg_recv_struct` | 接收结构体 |
| F65 | `func_delete_ipc_msg_queue` | 删除队列 |
| — | `func_process_exist_by_name` | (见4.5) |
---
## 5. 构建系统
### 层次结构 (与 RTU 完全一致)
```
release/
makefile ← 顶层入口 (SUBDIRS=./src)
linux.mk ← 公共编译选项 (CC/CXX/AR, x86/arm切换)
build.sh ← 一键编译脚本
src/
makefile ← 第二层 (SUBDIRS=public protocol system)
public/
makefile ← 第三层 (SUBDIRS=liblist libfunc liblog ...)
liblist/makefile
libfunc/makefile
...
protocol/
makefile
system/
makefile
```
### linux.mk 关键变量
- `CC`/`CXX`/`LD`/`AR` — 工具链
- `CROSS=arm` 切换 ARM 交叉编译
- `ROOT_DIR`/`SRC_ROOT_DIR`/`REL_INC`/`LIB_REL`/`EXE_REL` — 路径
- `C_FLAGS`/`CXX_FLAGS` — 编译选项(.c 用 gcc.cpp 用 g++
---
## 6. 模块分类:自研 vs 开源/项目库
> **重要**:以下分类决定模块是"自主从零实现"还是"直接使用现有开源库"
### 6.1 自研模块(需要从零实现)
| 模块 | 位置 | 语言 | 说明 |
|------|------|------|------|
| `libmd5` | public/ | 纯C | RFC 1321 MD5 摘要,需自主实现 |
| `libtask` | public/ | C/C++ | 事件/定时器/消息队列综合调度框架 |
| `libdatacenter` | public/ | C++ | 信号数据中心signal_in/out/ao/yk/param |
| `libcmd` | public/ | C++ | 命令注册/分发/linenoise交互框架 |
| `libcomm` | public/ | C++ | TCP/UDP/UART 通讯抽象层 |
### 6.2 开源/项目库(不需要重写,直接使用)
| 模块 | 位置 | 来源 | 说明 |
|------|------|------|------|
| `libcJSON` | public/ | cJSON | JSON 解析/生成,开源 MIT 许可证 |
| `libmy_xxhash` | public/ | xxHash | 极速哈希BSD 2-Clause 许可证 |
| `libxml` | public/ | tinyxml2 | XML 解析Zlib 许可证 |
| `libmy_mosquitto` | public/ | mosquitto | MQTT 客户端 |
| `libmongoose` | protocol/ | mongoose | 嵌入式 HTTP/WebSocket |
| `libmodbus` | protocol/ | libmodbus | Modbus RTU/TCP 协议栈 |
| `lib60870` | protocol/ | lib60870 | IEC 60870-5-101/103/104 协议栈 |
---
## 7. libmd5 自研模块详细功能
### 功能点(来自 RTU 原工程 `md5.c` 分析)
| 序号 | 函数 | 说明 |
|------|------|------|
| M01 | `StartMD5(ctx)` | 初始化上下文:设置魔数 A=0x67452301 等 |
| M02 | `EncodeMD5(ctx, input, len)` | 输入待计算数据,分块处理 64 字节块 |
| M03 | `GetMD5(ctx, result)` | 获取 16 字节 MD5 摘要 |
| M04 | `GetMD5String(ctx, output, mode)` | 格式化 hex 字符串mode=0 大写, 1 小写) |
| M05 | `MD5Transform`(内部) | 4 轮 × 16 步的 MD5 核心变换 |
| M06 | `Encode`(内部) | uint32 数组 → LE 字节序列 |
| M07 | `Decode`(内部) | LE 字节序列 → uint32 数组 |
### 数据结构
```c
typedef struct {
uint32_t state[4]; /* A, B, C, D */
uint32_t count[2]; /* 位计数 (low, high) */
unsigned char buffer[64]; /* 待处理数据缓冲 */
} MD5_CTX;
```
---
## 8. libtask 自研模块详细功能
### 功能点(来自 RTU 原工程 `myTask.c` + `myTask.h` 分析)
#### 8.1 定时器子系统
| 序号 | 函数 | 说明 |
|------|------|------|
| T01 | `task_timer_create(name, cb, arg, timeout_ms, flags)` | 创建定时器, flags=单次/周期 |
| T02 | `task_timer_start(ptimer)` | 启动定时器 |
| T03 | `task_timer_stop(ptimer)` | 停止定时器 |
| T04 | `task_timer_restart(ptimer, timeout_ms)` | 重启定时器(修改周期) |
| T05 | `task_timer_destroy(ptimer)` | 销毁定时器 |
| T06 | `task_timer_is_active(ptimer)` | 查询是否运行中 |
| T07 | `timer_manager_thread`(内部) | 独立线程轮询所有定时器到期 |
#### 8.2 事件子系统
| 序号 | 函数 | 说明 |
|------|------|------|
| T08 | `task_event_create(name)` | 创建事件对象 |
| T09 | `task_event_destroy(pevent)` | 销毁事件对象 |
| T10 | `task_event_send(pevent, event)` | 发送事件位 |
| T11 | `task_event_recv(pevent, set, opt, timeout_ms, recved)` | 等待事件, opt=AND/OR |
| T12 | `task_event_clear(pevent, event)` | 清除事件位 |
| T13 | `task_event_query(pevent)` | 查询当前事件位 |
#### 8.3 消息队列子系统
| 序号 | 函数 | 说明 |
|------|------|------|
| T14 | `task_msg_queue_create(name, msg_size, msg_num)` | 创建定长消息队列 |
| T15 | `task_msg_queue_destroy(pqueue)` | 销毁消息队列 |
| T16 | `task_msg_queue_send(pqueue, msg, size)` | 发送消息(阻塞) |
| T17 | `task_msg_queue_send_timeout(pqueue, msg, size, timeout_ms)` | 发送消息(超时) |
| T18 | `task_msg_queue_recv(pqueue, msg, size, timeout_ms)` | 接收消息(超时) |
| T19 | `task_msg_queue_try_recv(pqueue, msg, size)` | 尝试接收(非阻塞) |
| T20 | `task_msg_queue_get_count(pqueue)` | 获取队列当前消息数 |
| T21 | `task_msg_queue_space(pqueue)` | 获取队列剩余空间 |
#### 8.4 程序运行流程
```
定时器: timer_manager_thread 每 10ms 轮询所有活跃定时器 → 到期则回调
事件: 内部位图 + pthread_cond, send 置位+唤醒, recv 等待+清零
消息队列: 环形缓冲区 + pthread_mutex + pthread_cond
```
---
## 9. libcmd 自研模块详细功能
### 功能点(来自 RTU 原工程 `myCmd.h` + `my_cmd.cpp` 分析)
| 序号 | 函数/宏 | 说明 |
|------|---------|------|
| C01 | `cmd_manager_add_command(name, func, desc, complete)` | 注册命令 |
| C02 | `cmd_manager_get_commands(out_count)` | 获取所有已注册命令列表 |
| C03 | `cmd_help(argc, argv)` | 打印帮助信息 |
| C04 | `cmd_find(name)` | 按名称查找命令 |
| C05 | `cmd_complete(buf, completions, ncomp)` | Tab 自动补全回调 |
| C06 | `cmd_sub_complete(buf, completions, ncomp, subs, sub_count)` | 子命令补全 |
| C07 | `linenoise(prompt)` | 行编辑+历史交互式输入 |
| C08 | `linenoiseFree(ptr)` | 释放 linenoise 返回的内存 |
| C09 | `linenoiseHistoryAdd(line)` | 添加历史记录 |
| C10 | `linenoiseHistoryFree()` | 释放历史 |
| C11 | `CMD_REGISTER(name, func, desc)` | 宏:注册无补全的命令 |
| C12 | `CMD_REGISTER_C(name, func, desc, complete)` | 宏:注册带补全的命令 |
### 数据结构
```c
typedef struct {
const char *name; /* 命令名 */
void (*func)(int argc, char *argv[]); /* 回调 */
const char *desc; /* 描述 */
void (*complete)(...); /* 补全回调 */
} stru_cmd;
```
### 程序运行流程
```
命令注册 → cmd_manager_add_command → 存入全局命令哈希表
交互式Shell → linenoise(提示符) → 用户输入 → 查找命令 → 回调执行
Tab补全 → cmd_complete/cmd_sub_complete → linenoiseSetCompletionCallback
```
---
## 10. libcomm 自研模块详细功能
### 功能点(来自 RTU 原工程 myComm.h + comm/*.cpp 分析)
| 序号 | 函数 | 说明 |
|------|------|------|
| N01 | `comm_create(type, debug, para)` | 创建通讯实例(TCP/UDP/UART) |
| N02 | `comm_connect(id)` | 建立连接 |
| N03 | `comm_disconnect(id)` | 断开连接 |
| N04 | `comm_send(id, fd, data, len)` | 发送数据 |
| N05 | `comm_recv_register(id, cb)` | 注册接收回调 |
| N06 | `comm_state_register(id, cb)` | 注册状态变化回调 |
| N07 | `comm_destroy(id)` | 销毁实例 |
### 通讯类型枚举
```c
COMM_TYPE_TCP_SERVER, COMM_TYPE_TCP_CLIENT,
COMM_TYPE_UDP_SERVER, COMM_TYPE_UDP_CLIENT,
COMM_TYPE_UART
```
### 参数结构体
```c
stru_tcp_para { local_ip, local_port, remote_ip, remote_port }
stru_udp_para { local_ip, local_port, remote_ip, remote_port }
stru_uart_para { device, baudrate, data_bits, stop_bits, parity }
```
### 程序运行流程
```
comm_create → 按类型创建 TCP/UDP/UART 实例 → 返回 id
comm_connect → TCP 三次握手 / UDP bind / UART open
comm_send → 投递到对应 fd
接收线程 → 读 fd → 调用 comm_recv_cb 回调
状态变化 → 调用 comm_state_cb 回调(CONNECTED/DISCONNECTED)
```
---
## 11. libdatacenter 自研模块详细功能
> 基于 RTU 原工程 libdatacenter v1 与 libdatacenter_2 完整源码分析。
> 新工程采用纯 C 接口(`extern "C"` + C++ 内部实现,参数文件读写通过钩子解耦。
### 11.0 模块架构与设计思想
```
┌──────────────────────────────────────────────────────────────┐
│ 应用层(协议栈/Web/主程序) │
│ 调用 dc_xxx_register / dc_xxx_set_val / dc_run_100ms 等 │
├──────────────────────────────────────────────────────────────┤
│ libdatacenter纯 C 接口层) │
│ ┌──────────┬──────────┬──────────┬──────────┬────────────┐ │
│ │ out 信号 │ in 信号 │ yk 信号 │ ao 信号 │ param 信号 │ │
│ │ (遥测/ │ (关联 │ (遥控) │ (模拟 │ (定值/ │ │
│ │ 遥信) │ 输出) │ │ 输出) │ 参数) │ │
│ └──────────┴──────────┴──────────┴──────────┴────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 内部引擎hash查找 / 值校验 / 脏标记 / 变化检测 / 回路阻断│ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────┬──────────────┬────────────────────────────┐ │
│ │ 事件队列 │ 扰动队列 │ 故障队列 │ │
│ │ (SOE) │ (Disturb) │ (Fault) │ │
│ └──────────────┴──────────────┴────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 参数配置钩子(解析/保存/变更通知) │ │
│ │ 异步执行钩子(发起/结果/超时/取消) │ │
│ └──────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ 实现层C++: unordered_map + XXH3_128bits 哈希 + mutex
└──────────────────────────────────────────────────────────────┘
```
**核心约束**
- 信号唯一标识用 `saddr`(字符串,如 `"RPC:1:YC:1:val"`
- 每个信号注册时绑定 `module_id`(模块标识字符串),用于回路阻断
- out 信号值变更不是同步回调,而是**脏标记 + 100ms 延迟批量检测**(避免频繁回调)
- yk/ao/param 控制值变更才是**同步回调**(控制场景需要即时响应)
- 参数定值支持多区(如运行区/检修区),每区独立存储
### 11.1 数据结构
#### 11.1.1 控制枚举
```c
enum SIGNAL_CTRL_TYPE
{
NONE = 0, /* 无控制 */
DIRECT_NORMAL = 1, /* 直控(直接执行,无需选择-确认) */
SBO_NORMAL = 2, /* 选控(选择-确认两阶段SBO: Select Before Operate */
};
enum SIGNAL_CTRL_STEP
{
READY = 0, /* 就绪/空闲 */
SELECT = 1, /* 选择SBO 第一步:锁住信号,暂存选择值) */
DIRECT = 2, /* 执行SBO 第二步:确认后写入,或直控直接执行) */
CANCEL = 3, /* 取消(回退到 READY */
};
```
#### 11.1.2 控制上下文
```c
typedef struct
{
uint8_t step : 2; /* 当前控制步骤 SIGNAL_CTRL_STEP */
uint8_t type : 2; /* 控制类型 SIGNAL_CTRL_TYPE */
uint8_t backup : 4; /* 备用 */
uint8_t data_type; /* 数据类型 */
void *p_data; /* 暂存数据指针SELECT时保存选择值DIRECT时用于校验 */
} stru_signal_ctrl;
```
#### 11.1.3 信号参数元数据
```c
typedef struct
{
float min; /* 最小值 */
float max; /* 最大值 */
float step; /* 步长 */
char unit[32]; /* 单位(如 "kV", "A", "Hz" */
} stru_signal_param;
```
#### 11.1.4 内部信号节点
```c
typedef struct stru_signal
{
uint32_t id; /* 全局唯一ID递增分配用于 id_index 数组索引) */
XXH128_hash_t hash; /* 128位哈希saddr 的 XXH3_128bits */
char saddr[128]; /* 短地址 */
char desc[256]; /* 描述 */
uint8_t data_type; /* 数据类型 */
/* 数据指针列表out/in/yk/ao 为单元素param 为多区数组) */
void **vec_p_data; /* 动态数组:当前数据指针 */
int vec_p_data_count; /* 数据指针个数 */
void *p_last_data; /* 上一次数据快照out信号变更检测用 */
void **vec_p_default_data; /* 默认值指针param 恢复出厂用) */
/* 链接关系 */
char link_saddr[128]; /* in 信号:链接的 out saddr */
char **link_saddrs; /* out 被哪些 in 链接 */
int link_count;
uint8_t ctrl_type; /* 控制类型 SIGNAL_CTRL_TYPE */
stru_signal_param param; /* 参数元数据min/max/step/unit */
/* 回调列表(每个 cb 绑定 module_id 用于回路阻断) */
struct
{
char *module_id;
out_signal_change_cb out_cb; /* out 信号专用 */
signal_change_cb ctrl_cb; /* yk/ao/param 专用 */
} cb_list[16];
int cb_count;
char last_caller_module[64]; /* 最后修改本信号的模块ID回路阻断 */
} stru_signal;
```
#### 11.1.5 信号存储架构
```c
/* 每种信号类型一个独立的 mapsaddr→signal*+ id数组 */
typedef struct
{
pthread_mutex_t mtx;
uint32_t signal_id; /* 自增ID分配器 */
void *map_signals; /* C++ unordered_multimap<XXH128, signal*> */
stru_signal **id_index; /* id→指针 数组支持按ID遍历 */
int id_index_cap;
} stru_signal_map;
typedef struct
{
stru_signal_map signal_out;
stru_signal_map signal_in;
stru_signal_map signal_yk;
stru_signal_map signal_ao;
stru_signal_map signal_param;
} stru_datacenter;
```
### 11.2 模块生命周期与周期性函数
| 序号 | 函数 | 说明 |
|------|------|------|
| D00a | `dc_init(path)` | 初始化:解析参数配置文件(param.xml→self_param.xml),注册配置中的信号 |
| D00b | `dc_run_100ms()` | 每100ms① out信号变化检测+批量触发回调 ② 事件/扰动/故障队列弹出消费 |
| D00c | `dc_run_1000ms()` | 每1000ms检测参数变更标志有变更则写 self_param.xml |
### 11.3 信号注册5 种类型,均带 module_id
**关键设计**:所有注册函数必须传入 `module_id` 字符串,用于回路阻断——当模块 A 修改信号值时A 自身的回调不会被触发,只通知其他模块。
| 序号 | 函数 | 说明 |
|------|------|------|
| D01 | `dc_signal_out(saddr, desc, data_type, p_data, module_id)` | 注册输出信号(遥测/遥信) |
| D02 | `dc_signal_out_with_callback(saddr, desc, data_type, p_data, cb, module_id)` | 注册输出信号+变化回调(延迟触发,非同步) |
| D03 | `dc_signal_out_link_with_callback(saddr, pp_data, cb, module_id)` | 链接已有 out 信号,获取数据指针并注册回调 |
| D04 | `dc_signal_in(saddr, desc, link_saddr, pp_data, module_id)` | 注册输入信号,自动关联输出信号的数据指针 |
| D05 | `dc_signal_in_with_callback(saddr, desc, link_saddr, pp_data, cb, module_id)` | 输入信号+回调回调挂在被链接的out上 |
| D06 | `dc_signal_ao(saddr, desc, data_type, ctrl_type, p_data, cb, module_id)` | 注册模拟输出信号 |
| D07 | `dc_signal_ao_link_with_callback(saddr, pp_data, cb, module_id)` | 链接已有 ao 信号 |
| D08 | `dc_signal_param(saddr, desc, data_type, ctrl_type, pp_data, num, cb, module_id)` | 注册定值参数信号num为定值区数 |
| D09 | `dc_signal_param_link_with_callback(saddr, pp_data, num, cb, module_id)` | 链接已有 param 信号 |
| D10 | `dc_signal_yk(saddr, desc, data_type, ctrl_type, p_data, cb, module_id)` | 注册遥控信号 |
| D11 | `dc_signal_yk_link_with_callback(saddr, pp_data, cb, module_id)` | 链接已有 yk 信号 |
**注册流程**(以 out 为例):
```
dc_signal_out(saddr, desc, data_type, p_data, module_id)
→ 检查 saddr 是否已存在(重复注册报错)
→ 计算 XXH3_128bits(saddr)
→ 分配 p_last_data保存旧值快照用于变更检测
→ 插入 g_datacenter.signal_out
→ dc_set_param_cfg_change(true) /* ao/param 注册需标记配置变更 */
```
**in 信号关联逻辑**
```
dc_signal_in(saddr, desc, link_saddr, pp_data)
→ 查找 link_saddr 对应的 out 信号
→ (*pp_data) = out信号的 vec_p_data[0](共享同一数据指针)
→ 在 out 信号的 link_saddrs 中追加 in 的 saddr
→ signal.data_type = out.data_type数据类型跟随 out
```
### 11.4 信号控制(含 SBO 流程 + 值校验 + 回路阻断)
| 序号 | 函数 | 说明 |
|------|------|------|
| D12 | `dc_signal_ao_set_val(saddr, step, ctrl, p_data, module_id)` | 设置 AO 值(带 SBO 流程控制+值校验) |
| D13 | `dc_signal_ao_set_val_without_check(saddr, data_type, p_data, module_id)` | 设置 AO 值(跳过校验,用于初始化恢复) |
| D14 | `dc_signal_param_set_val(saddr, step, ctrl, zone, p_data, module_id)` | 设置定值(指定区号,带校验) |
| D15 | `dc_signal_param_set_val_without_check(saddr, data_type, zone, p_data, module_id)` | 设置定值(跳过校验) |
| D16 | `dc_signal_yk_set_status(saddr, step, ctrl, p_data, module_id)` | 设置遥控状态 |
**SBO 控制流程**
```
─── READY ──→ SELECT锁住信号暂存选择值到 ctrl.p_data
│ │
│ ├──→ CANCEL回退 READYctrl.step = READY
│ │
│ └──→ DIRECT校验值==选择值 → 写入 vec_p_data → ctrl.step=READY
└──→ DIRECT直控模式直接写入无需 SELECT
```
**值校验链**(以 `dc_check_val_valid` 为例):
1. 检查 zone 是否越界
2. 检查新旧值是否变化(无变化→拒绝)
3. 根据 data_type 检查值是否在 param.min/max范围内
4. 所有类型bool/int/float/double/IP/MAC/str全覆盖
**回路阻断机制**(核心设计):
```cpp
// 回调通知时,跳过发起修改的模块自身
for(auto &entry : p_signal->cb_list)
{
if (entry.first == module_id) continue; // 回路阻断:不发给自己
entry.second(saddr, step, data_type, zone, p_data);
}
```
**out 信号变化的延迟回调**(与 ao/yk/param 不同):
```
dc_set_out_signal_val(saddr, set_data, module_id)
→ p_signal->last_caller_module = module_id /* 记录调用方 */
→ dc_mark_signal_dirty(p_signal) /* 标记脏,不立即回调 */
→ 写入新值到 vec_p_data[0]
dc_run_100ms()
→ dc_signal_out_change_check()
→ 遍历 g_dirty_out_signals
→ 比较 vec_p_data[0] vs p_last_data
→ 有变化→通知cb_list跳过 last_caller_module
→ 更新 p_last_data
```
### 11.5 信号查询
| 序号 | 函数 | 说明 |
|------|------|------|
| D17 | `dc_get_out_signal_info(saddr, desc, desc_len, data_type, pp_data)` | 查询 out 信号 |
| D18 | `dc_get_in_signal_info(saddr, desc, desc_len, data_type, pp_data)` | 查询 in 信号 |
| D19 | `dc_get_ao_signal_info(saddr, desc, desc_len, data_type, p_param, ctrl_type, pp_data)` | 查询 ao 信号(含参数元数据) |
| D20 | `dc_get_param_signal_info(saddr, desc, desc_len, data_type, p_param, ctrl_type, pp_vec_data, p_count)` | 查询 param 信号(多区数据指针列表) |
| D21 | `dc_get_yk_signal_info(saddr, desc, desc_len, data_type, ctrl_type, pp_data)` | 查询 yk 信号 |
| D22 | `dc_get_signal_val(p_data, data_type, buf, buf_len)` | 信号值→字符串(纯 C 接口) |
| D23 | `dc_set_out_signal_val(saddr, set_data, module_id)` | 设置 out 信号值(脏标记+延迟回调) |
| D24 | `dc_get_signal_count(type_str)` | 按类型统计信号数("out"/"in"/"yk"/"ao"/"param" |
| D25 | `dc_get_signal_info_by_id(type_str, id, saddr, saddr_len, desc, desc_len, data_type_str, type_str_len, ctrl_type, link_str, link_len)` | 按ID查询信号基本信息供前端遍历 |
### 11.6 事件/扰动/故障队列
| 序号 | 类型 | 入队函数 | 注册消费回调 | 数据内容 |
|------|------|----------|-------------|----------|
| D26 | SOE 事件 | `dc_event_queue_push(p_soe)` | `dc_event_register_queue_pop(cb)` | saddr + status + 时间戳(sec,ms) |
| D27 | 扰动数据 | `dc_disturb_dd_queue_push(p_dd)` | `dc_disturb_dd_register_queue_pop(cb)` | saddr + fVal + 时间戳(sec,ms) |
| D28 | 故障录波 | `dc_fault_queue_push(p_fault)` | `dc_fault_register_queue_pop(cb)` | vec_soe + vec_disturb_yc |
**队列消费机制**
- 支持多个消费者(`vector<dc_queue_pop_cb>`
- `dc_run_100ms()` 中批量弹出消费,加锁 swap 减少锁持有时间
- push 时自动补时间戳sec==0 && ms==0 时自动获取当前时间)
### 11.7 参数配置文件解析与保存(解耦钩子设计)
**设计原则**参数文件读写通过钩子函数注册datacenter 不直接依赖 tinyxml2。
```c
/**
* @brief 参数配置文件提供者(由应用层注册)
* @param path 输出:返回配置文件的路径
* @param len 路径缓冲区大小
* @param buf 输出:返回配置文件的内容缓冲区
* @param size 输出:返回内容大小
* @return 0 成功,-1 没找到/不支持
*/
typedef int (*dc_param_cfg_read_cb)(char *path, int len, char **buf, int *size);
/**
* @brief 参数配置保存回调(由应用层注册)
* @param path 文件路径
* @param buf 要保存的内容
* @param size 内容大小
* @return 0 成功,-1 失败
*/
typedef int (*dc_param_cfg_save_cb)(const char *path, const char *buf, int size);
```
| 序号 | 函数 | 说明 |
|------|------|------|
| D29 | `dc_param_cfg_register_read_cb(cb)` | 注册参数配置读取回调 |
| D30 | `dc_param_cfg_register_save_cb(cb)` | 注册参数配置保存回调 |
| D31 | `dc_param_cfg_changed()` | 查询配置是否有未保存变更(返回 0/1 |
**配置文件语义**由钩子实现方保证datacenter 不解析格式):
- `param.xml`参数模板元数据saddr, desc, type, min, max, step, unit, default, value
- `self_param.xml`:用户运行时值(仅 saddr + value持久化动态修改
- 初始化时:先读 param.xml 的元数据注册信号 → 再读 self_param.xml 覆盖运行时值
- 配置变更时dc_param_cfg_check() 检测到变更标志 → 调 save_cb 写入 self_param.xml
### 11.8 类型系统工具函数
| 序号 | 函数 | 说明 |
|------|------|------|
| D32 | `dc_get_data_type_str_by_id(data_type)` | DATA_TYPE_U8→"uint8_t" |
| D33 | `dc_get_data_type_id_by_str("float")` | "float"→DATA_TYPE_F32 |
| D34 | `dc_get_data_type_len(data_type)` | 获取字节长度 |
| D35 | `dc_create_data_ptr_by_type(data_type)` | 根据类型 new 并清零 |
| D36 | `dc_delete_signal_data(p_data, data_type)` | 根据类型 delete |
| D37 | `dc_set_signal_val_from_str(p_data, data_type, str)` | 字符串→值("3.14"→3.14f |
| D38 | `dc_set_signal_val(p_data, data_type, set_data)` | 内存拷贝赋值 |
| D39 | `dc_data_compare(data_type, p_data1, p_data2)` | 类型感知的值比较 |
### 11.9 调试与导出
| 序号 | 函数 | 说明 |
|------|------|------|
| D40 | `dc_save_out_signals_xml(path)` | 导出所有已注册 out 信号到 XML供前端 PLC 配置读取) |
### 11.10 源文件对照表
| RTU原工程文件 | 新工程文件 | 说明 |
|---------------|-----------|------|
| `inc/dc_signal.h` | `inc/myDatacenter.h` | 合并为单头文件纯C接口 |
| `inc/dc_event.h` | ↑ | 合并 |
| `inc/dc_param.h` | ↑ | 合并 |
| `src/dc_signal.cpp` | `src/myDatacenter.cpp` | 信号注册主体 |
| `src/dc_signal_core.cpp` | ↑ | map操作+脏标记 |
| `src/dc_signal_ctrl.cpp` | ↑ | 值校验+控制流程 |
| `src/dc_signal_query.cpp` | ↑ | 信号查询+out变化检测 |
| `src/dc_signal_compare.cpp` | ↑ | 值比较+元数据应用 |
| `src/dc_signal_find.cpp` | ↑ | hash查找 |
| `src/dc_signal_util.cpp` | ↑ | 类型工具函数 |
| `src/dc_signal_show.cpp` | ↑ | 信号展示+cmd命令可选 |
| `src/datacenter.cpp` | ↑ | 初始化+生命周期 |
| `src/dc_param.cpp` | `src/dc_param_hook.cpp` | 参数配置文件钩子注册 |
| `src/dc_cfg_check.cpp` | ↑ | self_param.xml变更检测+保存 |
| `src/dc_event.cpp` | `src/dc_event.cpp` | 事件队列 |
| `src/dc_signal_internal.h` | `src/dc_signal_internal.h` | 内部共享声明 |
### 11.11 新旧工程差异对比
| 维度 | RTU 原工程 v1 | RTU 原工程 v2 | 新工程 libdatacenter |
|------|-------------|-------------|-------------------|
| 接口语言 | C++ (std::string) | 纯C (const char*) | 纯C (extern "C") |
| 哈希查找 | XXH128 multimap | XXH128 multimap | unordered_map + XXH3_128bits hasherO(1),复用 libmy_xxhash CBB |
| 参数文件 | tinyxml2 直接解析 | 同上 | 回调钩子解耦 |
| 异步执行 | 无 | 有_exec_async | 预留接口+钩子 |
| 回路阻断 | ✓ | ✓ | ✓ |
| module_id | std::string | const char* | const char* |
| 控制校验 | ✓ | ✓ | ✓ |
| SBO流程 | ✓ | ✓ | ✓ |
| 多区定值 | ✓ | ✓ | ✓ |
| 变更检测 | 脏标记+100ms | 同v1 | 同v1 |
| 调试命令 | cmd_dc | 无 | 预留 |
| 错误码 | -1/0 | enum dc_error_t | enum dc_error_t |
| 未实现 | — | — | 异步执行模型(预留)、tinyxml2绑定(钩子替代) |