RTU/mimo/问题处理文档.md

550 lines
27 KiB
Markdown
Raw Permalink 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.

# 问题处理文档
---
## 2026-07-07: PLC 逻辑图编辑器"生成配置"输出错误的 XML 配置
### 问题现象
在 PLC 逻辑配置页面(`#plc_config`)中,逻辑图可视化**查看器能正确解析配置文件**并展示逻辑图;但编辑器中点击**"生成配置"**后生成的 XML 配置文件不正确——多门组合链Chain 3中 AND#0、AND#1、OR#1 三个中间门及其输出信号全部丢失,只保留了链级门 NOT#1。
### 原因分析
`plc_config.js``generateXmlConfig()` 函数存在 4 个 Bug
1. **核心 Bug`combTerm` 逻辑错误**`combTerm` 要求门的**全部**下游连接都在 `cGates` 内部。但中间门 AND#1 的下游同时包含 OR#1和输出信号 1366导致 `downAllChain = false`,所有中间门被排除,`combTerm` 为空数组。
2. **`combXml` 递归缺少 `<Comb>` 包装** — 递归处理上游门时直接输出裸内容,未包裹在 `<Comb>` 标签中,导致生成的 XML 结构与解析器期望不匹配。
3. **链级 `<Outputs>` 包含所有输出** — 旧代码直接输出 `cOuts`(全部输出节点),但 Comb 内的输出信号1363-1367已在 `combXml` 中处理,链级只应包含 chainGate 的输出1368
4. **`delayMs` 提取截断** — `(n.id >> 8) & 0xFF` 只取低 8 位,延迟 > 255ms 时应为 `n.id >> 8`
### 解决方案
修改 `test/web_root/js/plc_config.js``generateXmlConfig()` 函数:
- **Bug #1**:将 `combTerm` 替换为 `combRoots`,基于 chainGate 的上游门计算 Comb 根节点(`combXml` 递归处理嵌套)
- **Bug #2**:在 `combXml` 递归调用处包裹 `<Comb>` 标签
- **Bug #3**:链级 `<Outputs>` 仅输出连接到 chainGate 的信号
- **Bug #4**`delayMs` 提取改为 `n.id >> 8`
### 验证
- 编译通过,浏览器测试确认生成的 XML 与原始配置完全一致
- 查看器可正确解析生成的配置23节点 20连线
- PLC 模块正常运行,所有输出信号处理正确
---
## 2026-07-01: com_channel_recv_cb 三级路由重构 (转发→bind→IEC)
### 需求背景
RTU 有 3 种数据通道,各自职责不同:
1. **uart0 ↔ STM32**: self_ptl ICP67 规约通讯
2. **tcp_s0 → uart0**: 维护软件透明隧道,仅转发 ICP67 + DEV_MAINTENANCE_ADDR 报文
3. **tcp_s1**: IEC 101/104 协议RTU 自行解析
原有问题:
- `com_recv_data` 做 ICP67/IEC 协议头分发,但 uart 数据是碎片帧,不应在此层区分
- 转发隧道双向化导致 uart→tcp 回环,且不校验维护软件报文有效性
- bind_app 的数据与协议头分发混在一起,职责不清
### 三级路由设计
```
com_channel_recv_cb():
1. 转发隧道 (from_saddr→to_saddr, 单向)
└→ 维护软件隧道: 校验 ICP67 + DEV_MAINTENANCE_ADDR, 通过则 comm_send
2. bind_app 直接路由
└→ uart0 bind="self_ptl" → MQ_COM_TO_SELF_PTL (不解析,不区分协议)
3. 兜底 IEC 101/104 协议检测
└→ tcp_s1 → com_recv_data → IEC 头检测
```
- **出境方向**: self_ptl 从 STM32 收到维护软件应答(dev_addr==DEV_MAINTENANCE_ADDR) → 通过 `g_p_tcp_s0_if` 转发给 tcp_s0正常 RTU 应答留在 self_ptl 处理
- **init1 阶段兼容**: p_event NULL 时数据只入队不发事件self_ptl 线程启动时排空队列
### 涉及文件
`decode_data_router.cpp`(三级路由+队列兼容), `decode_channel_mgr.cpp`(单向转发), `self_ptl.cpp`TCP 反查+队列排空), `channel_config.xml`(配置更新)
### 配置示例
```xml
<Channel mode="tcp_server" saddr="tcp_s0" desc="维护软件TCP转发隧道" bind=""/>
<Channel mode="tcp_server" saddr="tcp_s1" desc="IEC 101/104 TCP服务器" bind=""/>
<Channel mode="uart" saddr="uart0" desc="连接STM32的串口" bind="self_ptl"/>
<Forward from_saddr="tcp_s0" to_saddr="uart0" app="forward"/>
```
### 验证
RK 运行: `self_ptl_init: uart uart0 bind ok` / `tcp tcp_s0 via forward`,无 p_event NULL 刷屏,无 unknown protocol 错误
---
## 2026-07-01: self_ptl 通道绑定改为 bind 字段 + 数据路由防御性修复
### 需求背景
1. self_ptl 通过 Forward 规则的 `app="self_ptl"` 来查找通道,不配转发则找不到合理通道
2. `com_recv_data` 中硬编码 `"uart0"` 做通道区分,不应在数据路由层关心具体通道
3. 不配转发时 `com_scan_send_data_to_app``p_event` 为 NULL 导致异常
4. 串口噪声数据经 `com_recv_data` 因缺少 rx_len 校验导致协议误判
### 修复内容
#### 1. 通道 `bind` 字段 — 显式绑定私有规约
- **`mySystem.h`**: `stru_ch_cfg` 新增 `bind_app` 字段 + 声明 `channel_cfg_find_by_bind()`
- **`channel_cfg.cpp`**: 解析 XML `bind` 属性 + 实现 `channel_cfg_find_by_bind(bind_app, type)`
- **`self_ptl.cpp`**: `self_ptl_init()` 改为 `channel_cfg_find_by_bind("self_ptl", type)` 直接查找
- **`channel_config.xml`**: 通道增加 `bind="self_ptl"` 属性
- **效果**: 不再依赖 Forward 规则或自动检测,显式配置`bind="自协议名"`绑定通道
#### 2. com_recv_data 防御性修复
- **硬编码去除**: 删除 `channel_cfg_find_by_saddr("uart0")` 通道判断,统一走协议头分发
- **rx_len 校验**: 增加 `rx_len >= sizeof(stru_icp67_head)` 防不完整帧误判 0x67 头
#### 3. com_scan_send_data_to_app 空指针保护
- 拆分为三步逐项检查: `app_get_ptr``p_event` NULL → `task_event_send`,每步失败都有明确 LOG
### 涉及文件
`mySystem.h`, `channel_cfg.cpp`, `self_ptl.cpp`, `decode_data_router.cpp`, `channel_config.xml`
### 验证
x86 + ARM 编译通过RK 上运行日志显示 `self_ptl_init: uart uart0 bind ok` / `tcp tcp_s0 bind ok`
---
## 2026-07-01: channel_config 三合一优化:合并文件 + 双向转发 + 去除66转67
### 需求背景
1. channel_config.xmlx86和 channel_config_rk.xmlRK3568两套配置文件程序通过 `#ifdef RK356x` 宏区分
2. 转发通道只单向匹配 `from_saddr`,双向转发需配两条规则
3. 串口 66→67 帧转换逻辑在程序中实现,现由外部完成
### 修改内容
#### 1. 合并 channel_config 配置文件
- **文件**: `src/system/RTU/src/channel_cfg.cpp`
- **修改**: 删除 `#ifdef RK356x` 条件编译,统一加载 `channel_config.xml`
- **效果**: 只有一套配置文件,由使用者在 x86/RK 间自行调整 IP、端口、串口设备
#### 2. 转发通道双向互通
- **文件**: `src/system/libcom_decode/src/decode_channel_mgr.cpp`
- **修改**: `com_channel_get_send_info()` 中同时匹配 `from_saddr``to_saddr`,配一条 `tcp_s0→uart0` 自动实现双向转发
- **效果**: 配置文件只需写一条 Forward 即可实现两通道互通
#### 3. 去除 66→67 转换逻辑
- **文件**:
- `src/system/libcom_decode/src/decode_channel_mgr.cpp` — 删除 UART 返回 0 的 66→67 分支
- `src/system/libcom_decode/src/decode_data_router.cpp` — 删除 `else if(0 == result)` 分支,串口数据直接走 `com_recv_data` 协议解析
- `src/system/libcom_decode/src/decode_frame_codec.cpp` — 清空所有 66→67 帧转换函数(`icp66_frame_decode`、`icp66_search_frame`、`icp66_to_icp67`、`icp66_search_first_head`、`icp67_show_frame`、环形缓冲区等),保留文件占位
- `src/system/libcom_decode/inc/com_decode.h` — 删除 `icp66_frame_decode` 声明
### 涉及文件
`channel_cfg.cpp`, `decode_channel_mgr.cpp`, `decode_data_router.cpp`, `decode_frame_codec.cpp`, `com_decode.h`
### 验证
`./release/build.sh` x86 编译通过,零错误零警告
---
## 2026-06-30: lib60870 替换为 CBB 库后编译失败 — 类型未定义
**问题**`src/protocol/lib60870/` 替换为科大智能 CBB 库后,执行 `./release/build.sh` 编译失败,报大量 `unknown type name 'u8'/'u16'/'u32'/'BOOL'/'s16'/'s32'/'f32'` 等错误。
**根因**CBB 库的源文件(如 `gb103.c`、`gb103.h`)大量使用了 `u8`、`u16`、`u32`、`s16`、`s32`、`f32`、`BOOL`、`TRUE`、`FALSE` 等自定义类型/宏,但这些类型别名在原项目的 `lib60870_inc.h` 中未定义。CBB 库来自不同项目,依赖项目特定的 `ostype.h` 等公共头文件提供这些类型。
**解决方案**(不修改 CBB 库文件):
1. 创建项目自有兼容头文件 `release/inc/cbb_compat.h`,定义 CBB 库所需的类型别名:
```c
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef int16_t s16;
typedef int32_t s32;
typedef float f32;
typedef bool BOOL;
#define TRUE true
#define FALSE false
```
2.`release/src/protocol/lib60870/makefile` 中通过 gcc `-include` 注入该头文件:
```makefile
APP_C_FLAGS += -include cbb_compat.h
```
此方案**零修改 CBB 库源文件**,类型定义通过编译器注入,不影响库的独立性。
**调用方影响**`src/system/libiec/` 是 lib60870 的主要调用方,使用 `CS10x` 类型。CBB 库中 `CS10x` 通过 `typedef struct CS10x_t { ... } CS10x;` 正确定义,调用方代码兼容,无需修改。
---
## 2026-06-26: 远程调试 gdbserver 未自动启动 & RTU 打印信息查看问题
**问题**F5 选择 "ARM 编译+部署+调试 (RK3568)" 后无法断点调试。
**根因**`deploy-to-rk` 任务只做了编译+scp 传输,没有自动启动 gdbserver。VS Code GDB 尝试连接 `198.120.0.100:2345` 时端口无监听。
**修复**
1. 修改 `deploy-to-rk` 任务scp 传输后自动 `killall gdbserver` + 启动新 gdbserver
2. 修改 `start-gdbserver` 任务:统一用 `>/tmp/rtu_output.log 2>&1` 重定向 gdbserver+RTU 输出
3. 新增 `tail-rtu-output` 任务:一键 `tail -f /tmp/rtu_output.log` 实时查看 RTU printf 输出
**原理**RTU 作为 gdbserver 的子进程,继承 gdbserver 重定向后的 fd 1/2所以 printf/LOG_I/LOG_E 全部进入 `/tmp/rtu_output.log`
**涉及文件**`.vscode/tasks.json`
**注意事项**RTU 的 stdin 被 gdbserver 接管,无法在 tail 窗口输入 CLI 命令。交互式命令测试需单独 `ssh root@198.120.0.100 /mnt/RTU/RTU`
**验证**GDB 连接→continue→tail 文件成功看到所有 LOG_I/LOG_E 输出。
### 补充修复 (2026-06-26): "ARM 调试 (不编译)" preLaunchTask 卡住
**问题**F5 选 "ARM 调试 (不编译, RK3568)" 不工作,且后续引入复合任务后 "ARM 编译+部署+调试" 也失效。
**根因**:尝试用 `dependsOrder: "sequence"/"parallel"` + `isBackground: true` 复合任务作为 preLaunchTask。VS Code preLaunchTask 需要子任务明确完成信号,复合任务+后台子任务组合行为不可靠exit code 异常、卡住)。
**最终修复**
1. 删除所有复合任务 (`deploy-with-tail`, `start-gdbserver-with-tail`)
2. `deploy-to-rk` = scp + gdbserver同步preLaunchTask 可靠)
3. `start-gdbserver` = gdbserver同步preLaunchTask 可靠)
4. `tail-rtu-output` = 手动 `tail -f`(独立任务,不参与 preLaunchTask
5. `stopAtEntry` 改为 `false`RTU 直接运行不需手动按继续
**涉及文件**`.vscode/tasks.json`、`.vscode/launch.json`
**tail 使用方式**F5 启动调试后,`Ctrl+Shift+P` → Run Task → `tail-rtu-output` 查看实时打印。
---
## 2026-06-18: X-Macro 应用模块注册表 — 名称大小写匹配修复
**问题**X-Macro 生成 g_vec_app 名称时使用 `"app_" #name`(如 `app_SYS`),但配置文件 `app_config.json` 使用小写 `app_sys`,导致名称不匹配,所有线程被 "skipped (disabled by config)"。
**根因**C 预处理器 `#name` 运算符产生宏参数的原始文本,`APP_MODULE(SYS, ...)` → `"app_SYS"`,无法自动转小写。
**修复**:增加第二个参数 `cfg_name`(小写),与 `name`(大写,枚举后缀)分离:
```c
// 修改前: APP_MODULE(SYS, init1, init2, func)
// 修改后: APP_MODULE(SYS, sys, init1, init2, func)
```
- `#cfg_name``"app_sys"`(匹配配置文件)
**涉及文件**`app_modules.h`、`mySystem.h`(两处)、`app_sys.cpp`(一处)
**注意事项**:旧进程残留占端口会导致 `mms_s_init` 绑定失败。app_modules.h 禁止加 `#pragma once`
**验证**编译通过8 个线程全部 `pthread_create success`
---
## 2026-06-16: ICP67 模块多实例重构
**来源**[libicp67模块分析](./工程/libicp67模块分析.md)
### 修复项
1. **全局回调表单例化** — 将 `g_genneral_method` 从 static 全局变量移入 `stru_icp67.method`。26 个 setter 改为实例化接口(`stru_icp67 *` 第一参数)。删除 `general_method.h`、`icp67_get_genneral_method()`。所有 decode 函数改为 `p_icp67->method.xxx_cb` 回调。
2. **重传竞态修复**`icp67_timer_handler``resend_cnt++`/`tm_cnt=0`/`tm_cnt++` 移入 `rtx_sem` 保护区。
3. **发送覆盖防御**`icp67_rtx_flag_set` 增加 TX_FLAG 重复设置 LOG_E 警告。
4. **空桩填充**`icp67_decode_ti_4`、`icp67_decode_ti_203` 改为 LOG_E + ICP67_RX_FLAG 设置。
5. **硬编码清理** — 所有魔数替换为命名宏(`ICP67_TX_BUF_SIZE`、`ICP67_MD5_LEN`、`ICP67_TM_TICK_MS`/`ICP67_TM_OUT_MS`、`ICP67_HEAD_OVERHEAD`/`ICP67_FRAME_OVERHEAD` 等)。
6. **配置参数宏化** — 超时/重试/缓冲/定时器 tick 统一在 `myIcp67.h` 中用 `#define` 管理。
7. **sender 函数迁移** — 12 个内部 sender 函数从 `general_method.cpp` 迁入 `icp67.cpp``icp67_init` 中初始化。
**状态**:✅ 已完成
**涉及文件**`myIcp67.h`, `icp67.h`, `icp67.cpp`, `general_method.cpp`, `self_ptl.cpp`, `method.cpp`, `general_method.h`(已删除)
**设计决策**
- pack(1) 不转换ARM/x86 小端固定,风险收益比不成立)
- TI_11 len==11 注释不启(子函数已各自处理标志)
- dispatch map`g_map_ti_decode` 等)保持 staticread-only多实例安全
### #11 libcom_channel + libcom_scan 合并为 libcom_decode
**问题**两个模块紧耦合com_channel 调 com_recv_datacom_scan 调 com_channel_interface_get且 app_comm_channel 线程为空壳
**修复**
1. 合并为 `libcom_decode` 模块,按功能拆分 4 个文件
2. 整合为 1 个 app 线程(删除空壳线程)
3. 枚举值合并ENUM_APP_COMM + ENUM_APP_COM_SCAN → ENUM_APP_COM_DECODE
**状态**:✅ 已完成
**涉及文件**:新建 `src/system/libcom_decode/`4源文件+makefile修改 `mySystem.h`、`app_sys.cpp`、`self_ptl.cpp`、`iec.cpp`、`makefile`
**验证**`./release/build.sh` 编译通过
---
### #10 libcomm + 调用者缺陷修复
**问题**
1. UDP `recvfrom` 永久阻塞,无超时无退出机制
2. UDP `close``state_cb(id, -1, disconnected)`,回调拿到的是已关闭的 -1
3. `com_scan`/`self_ptl`/`iec` 三处 send+event 回滚竞态:`msg_queue_send` 成功 → `event_send` 失败 → `try_recv` 可能取出旧消息
4. `com_channel_recv_cb``comm_send` 到已断开的 TCP_C_0 不检查 fd
**修复**
1. `udp_run``recvfrom` 前加 `select` 1s 超时
2. `udp_close` 先保存 fd 再 close回调传入正确值
3. 三处改为先 `event_send``msg_queue_send`,消除回滚竞态
4. `com_channel_recv_cb``comm_send` 前检查 `socket_fd >= 0`
**状态**:✅ 已完成
**涉及文件**`comm_udp.cpp`, `com_scan.cpp`, `self_ptl.cpp`, `iec.cpp`, `com_channel.cpp`
**验证**`./release/build.sh` 编译通过
---
### #9 libcomm 模块修补
**问题**
1. UART send 不完整:裸 write 无流量控制,缺少原始模式终端设置(~ICANON/~ECHO/~OPOST
2. TCP client select 100ms 空轮询,空闲时 CPU 空转
3.`comm_destroy` 接口,内存泄漏
4. `stru_comm` 结构体中 6 个函数指针字段从未使用,冗余
**修复**
1. 重写 `comm_uart.cpp`:恢复原始模式 + VTIME=1 读超时 + tcflush/tcdrain 完整发送
2. TCP client select 去掉超时参数,永久阻塞
3. 新增 `comm_destroy(id)` API
4. 删除 `stru_comm` 中 6 个未使用字段
**状态**:✅ 已完成
**涉及文件**`comm_uart.cpp`, `comm_tcp.cpp`, `comm.cpp`, `comm.h`, `myComm.h`
**验证**`./release/build.sh` 编译通过
---
### #8 libtask 事件/消息队列缺陷修复
**问题**
1. `task_event_destroy``task_msg_queue_destroy``free(p)` 后仍访问 `p->name`use-after-free
2. `task_event_recv` 中 AND 检查用 `opt == TASK_EVENT_FLAG_AND` 而非位测试,`AND | CLEAR` 组合被误识别为 OR
3. `task_event_send``pthread_cond_signal` 而非 `cond_broadcast`,多等待者场景可能唤醒不足
**修复**
1. `free(p)` 移到 `LOG_I` 之后,先打日志再释放
2. `opt ==` 改为 `opt &` 位测试
3. `cond_signal` 改为 `cond_broadcast`
**状态**:✅ 已完成
**涉及文件**`src/public/libtask/src/myTask.c`
**验证**`./release/build.sh` 编译通过
---
### #7 libtask 定时器 SIGEV_THREAD 线程爆炸
**问题**`myTask.c` 使用 `timer_create(CLOCK_REALTIME, SIGEV_THREAD)` 模式每次定时器超时内核创建一个新线程执行回调。9 个 app 线程 × 3 个定时器10ms/100ms/1000ms= 27 个定时器,每秒约 1000 次内核线程创建/销毁,系统开销极大。回调仅做 `task_event_send` 设一个事件位(几微秒),线程创建开销远大于实际工作。
**需求**
1. 消除每次定时器超时创建线程的开销
2. API 签名全部不变,调用方零修改
3. 仅 Linux 平台
**处理计划**[libtask定时器SIGEV_THREAD优化](./plan/libtask定时器SIGEV_THREAD优化.md)
**修复方案**:用 Linux **timerfd** + **epoll** + **单例管理器线程** 替代 `SIGEV_THREAD`
- `stru_task_timer``timer_t timerid` → `int timerfd`,新增 `pthread_cond_t cond`、`int in_epoll`
- 删除 `task_timer_sig_handler`,新增 `timer_manager_thread`epoll_wait 监听所有 timerfd超时时调用原始回调
- `task_timer_stop/destroy``while(running) usleep(1000)` 忙等 → `cond_wait` 零 CPU 阻塞
- 6 个 API 签名全部不变
**状态**:✅ 已完成
**涉及文件**`src/public/libtask/src/myTask.c`(仅此一个文件,`release/inc/myTask.h` 无改动)
**验证**`./release/build.sh` 编译通过,零错误零警告
---
## 2026-06-12
### #6 Tab 补全子命令前缀丢失
**问题**:输入 `datacenter ` + `p` + Tab 后,`datacenter ` 前缀被 `param` 覆盖,整行只剩 `param`
**根因**`linenoiseEdit` 中 Tab 键处理用 `strncpy(buf, completions[0])` 整行覆盖。
**修复**:改为找到最后一个空格,只替换空格之后的当前词,保留前缀。
---
### #5 app_cmd 交互卡顿与 Tab 补全失效
**问题**:上一轮将 `cmd_recv` 改为 select 非阻塞 + EV_TIMER2(100ms) 后:
1. 回车后 `cmd>` 回显不及时,有明显卡顿感
2. 输入 `d` 后按 Tab不会弹出命令补全
**根因**`select()` 方案与 `linenoise()` 交互式行编辑器根本性不兼容——select 时终端处于规范模式行缓冲Tab 不是行终止符不会触发 select100ms 定时器引入最多 100ms 延迟。
**修复**:将 `app_cmd` 线程从定时器事件驱动改为简单阻塞循环 `while(1) { cmd_recv(); }`。`linenoise()` 自管理终端模式切换和 Tab 补全,线程阻塞在 stdin 上零延迟响应。
**验证**:编译通过。
---
### #4 app_cmd 线程启用后 CPU 飙升至 108%
**问题**`app_cmd` 线程中 `cmd_recv()` 启用后CPU 占用 108%,导致该入口被暂时屏蔽([app_cmd.cpp:117](src/system/RTU/src/app_cmd.cpp#L117) 被注释)。
**根因**
1. **死循环层面**`linenoiseEdit()`[my_cmd.cpp:107](src/public/libcmd/src/my_cmd.cpp#L107))中 `read()` 只检查了返回 `-1`,未处理返回 `0`EOF。当进程无真正控制终端时RTU 嵌入式环境常见),`read()` 立即返回 0 形成死循环。
2. **架构层面**`linenoise()` 是同步阻塞调用,却被放在 10ms 定时器 EVI_TIMER1 中执行,与其他定时器事件循环模型不兼容。
**修复内容**
1. `my_cmd.cpp``read()` 返回值判断改为 `<= 0`(含 EOF 处理);`enableRawMode()` 返回值在 `linenoise()` 中检查,失败直接返回 NULL
2. `app_cmd.cpp`:重构 `cmd_recv``cmd_recv_nonblock()`,用 `select()` 实现非阻塞 stdin 检查 + `isatty()` 过滤非终端环境,移至 EV_TIMER2100ms调用
3. `CLAUDE.md`:全文翻译为中文
**验证**:编译通过(零错误零警告),`./test/RTU < /dev/null` 运行 3 秒 CPU 占用 0.0%。
---
## 2026-06-18: 跨模块耦合解耦 — self_ptl_set_interface → dc_signal + app_get_ptr → app_get_mq_target
**问题**:系统模块间存在两种硬编码耦合:
1. `decode_channel_mgr` 直接调用 `self_ptl_set_interface()` 传递 TCP 接口 → 强依赖 self_ptl 头文件
2. `iec.cpp`/`self_ptl.cpp`/`decode_data_router.cpp` 中通过 `app_get_ptr(ENUM_APP_XXX)` + 硬编码事件号发送跨模块通知 → 模块间直接知晓对方身份
**根因**早期快速迭代中模块间交互未经过中间层抽象。协议处理模块iec/self_ptl/comm互相知道对方的应用 ID、事件号、接口函数。
**修复**
### 问题一self_ptl_set_interface 解耦
- `decode_channel_mgr.cpp`:移除 `#include "self_ptl.h"``self_ptl_set_interface()` 调用,改为 `dc_signal_out("sys.tcp_c0_interface", ...)` 通过 datacenter 发布接口信息
- `self_ptl.cpp`:移除 `self_ptl_set_interface()` 函数,`self_ptl_init()` 改为通过 `dc_get_out_signal_info("sys.tcp_c0_interface")` 从 datacenter 读取接口
### 问题二app_get_ptr + 硬编码事件号解耦
- `mySystem.h`:新增 `stru_mq_target {app_id, event}` 结构和 `app_get_mq_target(enum_mq_id)` 声明
- `app_sys.cpp`:新增 `g_mq_target_map[ENUM_MQ_MAX]` 全局映射表和 `app_get_mq_target()` 实现
- `iec.cpp``iec_data_tx()` 中 `app_get_ptr(ENUM_APP_COM_DECODE) + EV_COM_RX_IEC``app_get_mq_target(ENUM_MQ_IEC_TO_COM)`
- `self_ptl.cpp``self_ptl_data_tx()` 中硬编码事件号 → `app_get_mq_target(ENUM_MQ_SELF_PTL_TO_COM)`
- `decode_data_router.cpp``com_scan_send_data_to_app()` 中 if/else 判断目标 MQ → 通过 `app_get_mq_target(mq_id)` 统一处理
**状态**:✅ 已完成
**涉及文件**`decode_channel_mgr.cpp`, `self_ptl.cpp`, `iec.cpp`, `decode_data_router.cpp`, `mySystem.h`, `app_sys.cpp`
**验证**`./release/build.sh` 编译通过,运行日志显示 `self_ptl_init got tcp_c0_if from dc: 20006` 确认 datacenter 信号通信正常。
---
## 2026-06-10
### #1 libmms_m RCB 订阅编号硬编码
**问题**`mms_m_icd_report_init()` 中硬编码 `if(0 == rpt_no.compare("01"))`,只订阅编号为 `"01"` 的 RCB其他编号被丢弃无法按需灵活订阅多个 RCB 实例。
**需求**
1. 灵活可配置订阅的控制块编号
2. libmms_m 提供接口,由 libiec61850m 传入
3. 可传入一个或多个编号,不传默认 `"01"`
4. 无效数据打印错误并返回失败
**处理计划**[RCB订阅编号可配置化](./mid/RCB订阅编号可配置化.md)
**状态**:✅ 已完成
**涉及文件**`myMms_m.h`, `mms_m.h`, `mms_m.cpp`, `iec61850m.cpp`
---
### #2 libweb_server 模块缺陷修复
**问题**WebSocket 服务端模块存在 10 个缺陷含严重级别单客户端、多线程竞态、悬空指针、non-null-terminated UB和高危/中等级别(调试 printf、SBO 阻塞、LOG 格式化缺失等)。
**需求**
1. 兼容多个 WebSocket 客户端同时连接
2. 完善断连处理
3. 多线程安全保护
4. 修复 UB 和格式化问题
5. 增量推送优化(仅变化时发送)
**处理计划**[libweb_server模块分析](../工程/libweb_server模块分析.md)
**状态**:✅ 已完成
**涉及文件**`web_server.cpp`, `ws_method.cpp`
---
### #3 libweb_server 多连接资源共享冲突
**问题**:支持多客户端后,所有连接共享同一套全局信号资源(`g_ws_out_signals` 等),一个客户端的 add/del 操作会影响其他客户端的数据推送。
**需求**
1. 每个连接独立的信号资源per-connection session
2. 连接建立时自动开辟资源,断开时自动释放
3. `ws_task()` 按 session 独立构建 JSON 推送到对应连接
**处理计划**[libweb_server模块分析](../工程/libweb_server模块分析.md)
**状态**:✅ 已完成
**涉及文件**`ws_method.h`, `ws_method.cpp`, `web_server.cpp`
---
---
## 2026-06-16: Self_PTL 缺陷优化
**来源**[libself_ptl模块分析](./工程/libself_ptl模块分析.md)
### 修复项
1. **D4 测试命令分离**`self_ptl_method_task()` 增加 `g_self_ptl_test_active` 守卫,仅当 CLI 触发测试命令(`cmd_self_ptl` 设置 flag时才进入遍历TIMER2 先检查 active 标志再调用。
2. **D2 环形缓冲区加锁**`stru_self_rx` 增加 `pthread_mutex_t mutex` 字段,`put_rx_data`/`get_rx_data` 操作加锁保护。
3. **D3 溢出日志启**`put_rx_data` 溢出处理中原被注释的 `LOG_E` 已启。
4. **D8 SBO 超时**`stru_signal_ctrl` 增加 `select_time_ms` + `sbo_timeout_ms` 字段。`dc_check_ctrl_valid` 在 SELECT 时记录时间戳。新增 `dc_signal_check_sbo_timeout()` 函数。`self_ptl_task()` 每定时器 tick 检查 SBO 状态,超时(默认 30s自动回退到 READY。
5. **D6 硬编码清理**`2048``SELF_PTL_BUF_SIZE``256` → `SELF_PTL_TEMP_SIZE`IEC-104 APCI 魔数 → `IEC_APCI_MIN_LEN`/`IEC_FRAME_OVERHEAD`。
### 未处理
- **D7 双缓冲优化** — 改动大、风险高,暂缓。
- **D5 IEC-104 帧校验** — 已完整,无需处理。
- **D9 滑窗丢弃** — 逻辑正确,无需处理。
**状态**:✅ 已完成
**涉及文件**`self_ptl.cpp`, `method.cpp`, `myDatacenter.h`, `dc_signal.cpp`
---
## 2026-06-16: libdatacenter 缺陷修复
**来源**[libdatacenter模块分析](./工程/libdatacenter模块分析.md)
### 修复项
1. **缺陷1&2: AO/Param 重注册持锁外 delete**`dc_signal_ao``dc_signal_param` 的 re-registration 路径中 `dc_delete_signal_data` 改为在 `signal_ao.mtx`/`signal_param.mtx` 锁内执行,消除 use-after-free 风险。
2. **缺陷3: 回调向量无锁写入**`out_change_cb_list`/`change_cb_list` 的 `push_back` 加对应的 `stru_signal_map.mtx` 锁保护。
3. **缺陷4: YK 跳过值校验**`dc_signal_yk_set_status` 的 DIRECT 步骤增加 `dc_check_val_valid` 调用,与 AO/Param 行为一致。
4. **缺陷5: strncpy 安全** — IP/MAC/C 类型 `dc_set_signal_val` 中的 `strncpy` 改为 `memcpy`,避免源串等于缓冲区长时无 `\0` 终止。
**状态**:✅ 已完成
**涉及文件**`src/system/libdatacenter/src/dc_signal.cpp`
---
## 2026-06-16: 参数缺省值重构
**来源**:设计讨论——缺省值应只存在于配置文件,不应在运行时存储。
### 完成项
1. **移除 `stru_signal.vec_p_default_data`** — 默认值不再存储于运行时结构中
2. **移除 API 默认值参数**`dc_signal_ao()` / `dc_signal_param()` 不再接收 `p_default_data``dc_get_ao_signal_info()` / `dc_get_param_signal_info()` 不再输出默认值
3. **删除 `dc_signal_{ao,param}_add_check()`** — 不再需要比较默认值变化
4. **dc_param.cpp 不再创建默认值** — 初始化时只从 `param.xml`/`self_param.xml` 读值并注册
5. **self_ptl 不再创建/传默认值** — 删除 `vec_p_default_data` 创建和传参
6. **ws_method 去掉默认值** — 删除读取/展示/序列化
7. **iec61850m 不再创建默认值** — 删除 `p_default[]` 创建
8. **self_param.xml 不写 default 字段**`dc_param_cfg_check()` 只写 `value`
**状态**:✅ 已完成
**涉及文件**`dc_signal.h/cpp`, `myDatacenter.h`, `dc_param.cpp`, `self_ptl.h/cpp`, `method.cpp`, `ws_method.cpp`, `iec61850m.cpp`, `iec61850s.cpp`