# ICP67 模块多实例重构方案 ## 概述 将 `libicp67` 从全局单例回调架构重构为多实例安全架构,同时修复重传竞态 bug、填充空桩、统一硬编码为命名常量。 --- ## 涉及文件 | 文件 | 改动量 | |------|--------| | `release/inc/myIcp67.h` | 添加宏常量、添加 method 字段到 stru_icp67、修改 setter 声明 | | `src/protocol/libicp67/src/icp67.cpp` | 移除 `icp67_get_genneral_method()` 调用、修复重传竞态、填充空桩 | | `src/protocol/libicp67/src/general_method.cpp` | 删除 `g_genneral_method`、setter 改为实例化、method 初始化移入 icp67_init | | `src/protocol/libicp67/inc/general_method.h` | 删除文件(不再需要) | | `src/protocol/libicp67/inc/icp67.h` | 添加内部常量宏 | | `src/system/libself_ptl/src/self_ptl.cpp` | setter 调用增加 p_icp67 参数、get 类回调改为直接通过 p_icp67->method 访问 | | `src/system/libself_ptl/src/method.cpp` | get 类回调调用方式调整 | | `src/system/libcom_decode/src/decode_frame_codec.cpp` | 更新 `icp67_show_frame()` 路径引用 | --- ## 任务分解 ### T1: 添加常量宏定义 **文件**: `release/inc/myIcp67.h`、`src/protocol/libicp67/inc/icp67.h` 在 `myIcp67.h` 中新增: ```c #define ICP67_TX_BUF_SIZE 2048 // tx/resend_tx 缓冲区大小 #define ICP67_RESEND_MAX 5 // 重发最大次数 #define ICP67_TM_TICK_MS 10 // 定时器 tick 周期 (ms) #define ICP67_TM_OUT_MS 1000 // 重传超时 (ms) #define ICP67_MD5_LEN 16 // MD5 长度 ``` 在 `icp67.h` 中新增: ```c #define ICP67_HEAD_OVERHEAD 4 // head1(1)+len(2)+head2(1) #define ICP67_FRAME_OVERHEAD 6 // head_overhead(4)+crc(1)+tail(1) #define ICP67_UPGRADE_TYPE 1 // 升级类型 #define ICP67_NAME_MAX 256 // 文件名最大长度 #define ICP67_TM_STRUCT_LEN 7 // 时间结构体长度 ``` 将所有魔数替换为宏或 `sizeof()`(`pos += 4` → `pos += sizeof(uint32_t)`; `pos += 2` → `pos += sizeof(uint16_t)`;`16` → `ICP67_MD5_LEN` 等)。 `stru_icp67` 中的 `tx[ICP67_TX_MAX_LEN]`、`resend_tx[ICP67_TX_MAX_LEN]` 改为 `[ICP67_TX_BUF_SIZE]`,`md5[16]` 改为 `md5[ICP67_MD5_LEN]`。 --- ### T2: 将 stru_genneral_method 移入 stru_icp67 **文件**: `release/inc/myIcp67.h` 在 `stru_icp67` 结构体中添加字段: ```c typedef struct _icp67 { void *arg; icp67_send_cb send_cb; icp67_search_frame_cb search_frame_cb; icp67_decode_cb decode_cb; icp67_timer_handler_cb timer_handler_cb; stru_genneral_method method; // ← 新增,替代全局 g_genneral_method uint8_t md5[ICP67_MD5_LEN]; uint8_t tx[ICP67_TX_BUF_SIZE]; // ... 其余不变 }icp67; ``` --- ### T3: 重构 setter/getter 为实例化接口 **文件**: `src/protocol/libicp67/src/general_method.cpp`、`release/inc/myIcp67.h` **变更**: 1. **删除** `g_genneral_method` 全局变量和 `icp67_get_genneral_method()` 函数。 2. **删除** `general_method.h` 文件(不再需要 `icp67_get_genneral_method()` 前向声明)。 3. **"pop_out/trans" 类 setter**(17个):参数增加 `stru_icp67 *p_icp67`,写入 `p_icp67->method.xxx_cb`。示例: ```c void icp67_set_mx_trans_cb(stru_icp67 *p_icp67, icp67_mx_trans_cb cb) { p_icp67->method.mx_trans_cb = cb; } ``` 4. **"get" 类 setter**(12个):不再从全局表"拉取"到 caller 结构体。改为让 caller 直接通过 `p_icp67->method.xxx_cb()` 调用。这些 setter 变为空操作或删除——caller 直接访问 `g_self_ptl.icp67.method.ao_get_cb(...)`。 5. **更新 `myIcp67.h` 中所有 extern 声明**:增加 `stru_icp67 *p_icp67` 第一参数。"get" 类 12 个 setter 删除声明。 6. **method 初始化**:在 `icp67_init()` 中将 `p_icp67->method` 的 sender 字段初始化为内部函数指针,pop_out/trans 字段初始化为 NULL。 ```c // icp67_init 中新增 p_icp67->method.ao_get_cb = icp67_ao_get; p_icp67->method.ao_set_cb = icp67_ao_set; // ... 13 个 sender 初始化 ``` 7. **将 internal sender 函数**从 `LOCAL` 改为公开(或提供 `extern` 声明),以便 `icp67_init` 可以引用它们。在 `icp67.cpp` 中声明: ```c extern void icp67_ao_get(stru_icp67 *p, stru_ti_1_2_data *d, uint16_t n); extern void icp67_ao_set(stru_icp67 *p, uint8_t *d, uint16_t n); // ... 13个 ``` 8. **更新 `general_method.cpp`**:删除 `g_genneral_method` 初始化代码,将 13 个 sender 函数删除 `LOCAL`(或使用条件编译保留 static 内部链接,在 icp67.cpp 中使用函数指针表初始化)。 --- ### T4: 重构 decode 函数——从 instance 读回调 **文件**: `src/protocol/libicp67/src/icp67.cpp` 所有 decode 函数中,将: ```c stru_genneral_method *p_genneral_method = icp67_get_genneral_method(); p_genneral_method->ao_pop_out_cb(...); ``` 改为: ```c p_icp67->method.ao_pop_out_cb(...); ``` 涉及 16 个 decode 函数。移除 `#include "general_method.h"`。 --- ### T5: 修复重传竞态(Bug 1) **文件**: `src/protocol/libicp67/src/icp67.cpp`,`icp67_timer_handler()`(~line 960-983) 将 `resend_cnt++` 和 `tm_cnt = 0` 从信号量外移入保护区内: **方案**:在 `icp67_rtx_flag_set` 中增加参数控制 resend 递增,或在 timer handler 中通过一个新的受保护操作完成。 **推荐方案**:在 `icp67_rtx_flag_set` 增加重载或新函数 `icp67_rtx_resend_inc(stru_icp67 *p_icp67)`: ```c void icp67_rtx_resend_inc(stru_icp67 *p_icp67) { sem_wait(&p_icp67->rtx_sem); p_icp67->resend_cnt++; p_icp67->tm_cnt = 0; sem_post(&p_icp67->rtx_sem); } ``` timer handler 中调用此函数而非直接操作字段。`tm_cnt++`(line 982)也移入 `rtx_sem` 保护: ```c void icp67_rtx_tm_inc(stru_icp67 *p_icp67) { sem_wait(&p_icp67->rtx_sem); p_icp67->tm_cnt++; sem_post(&p_icp67->rtx_sem); } ``` timer handler 调用: ```c icp67_rtx_tm_inc(p_icp67); icp67_rtx_flag_get(p_icp67, rtx_flag, resend_cnt, tm_cnt); if (rtx_flag == ICP67_TX_FLAG && resend_cnt < ICP67_RESEND_MAX && tm_cnt >= p_icp67->tm_out) { p_icp67->send_cb(p_icp67->resend_tx, p_icp67->resend_tx_len, p_icp67->arg); icp67_rtx_resend_inc(p_icp67); LOG_I("resend cnt %d", p_icp67->resend_cnt); icp67_show_frame(p_icp67->resend_tx, p_icp67->resend_tx_len); } ``` --- ### T6: 发送新请求覆盖旧 resend_tx 防御(Bug 3) **文件**: `src/protocol/libicp67/src/icp67.cpp`,`icp67_rtx_flag_set()` 在函数内增加 LOG_W 防御: ```c void icp67_rtx_flag_set(stru_icp67 *p_icp67, uint8_t flag, uint8_t resend_cnt, uint32_t tm_cnt) { sem_wait(&p_icp67->rtx_sem); if (flag == ICP67_TX_FLAG && p_icp67->rtx_flag == ICP67_TX_FLAG) { LOG_W("icp67_rtx_flag_set: overwriting pending TX, prev resend_cnt=%d", p_icp67->resend_cnt); } p_icp67->rtx_flag = flag; p_icp67->resend_cnt = resend_cnt; p_icp67->tm_cnt = tm_cnt; if (flag == ICP67_TX_FLAG) { memcpy(p_icp67->resend_tx, p_icp67->tx, p_icp67->tx_len); p_icp67->resend_tx_len = p_icp67->tx_len; } sem_post(&p_icp67->rtx_sem); } ``` --- ### T7: 填充空桩函数 **文件**: `src/protocol/libicp67/src/icp67.cpp` **TI_4** (`icp67_decode_ti_4`, line 196-199): ```c LOCAL int icp67_decode_ti_4(stru_icp67 *p_icp67, uint8_t *p_rx, uint16_t rx_len) { LOG_W("TI_4 not supported"); icp67_rtx_flag_set(p_icp67, ICP67_RX_FLAG, 0, 0); return 0; } ``` **TI_203** (`icp67_decode_ti_203`, line 434-437): ```c LOCAL int icp67_decode_ti_203(stru_icp67 *p_icp67, uint8_t *p_rx, uint16_t rx_len) { LOG_W("TI_203 not supported"); icp67_rtx_flag_set(p_icp67, ICP67_RX_FLAG, 0, 0); return 0; } ``` **TI_206**:保持死代码不变(不注册到 map,不删除)。 --- ### T8: 更新调用方 self_ptl **文件**: `src/system/libself_ptl/src/self_ptl.cpp`、`inc/method.h`、`src/method.cpp` **self_ptl.cpp** (`self_ptl_init()` 函数): 1. **"pop_out/trans" 类**(17个):每个setter 增加 `p_icp67` 第一参数: ```c icp67_set_ao_pop_out_cb(p_icp67, p_method->ao_pop_out_cb); icp67_set_iec_point_tbl_pop_out_cb(p_icp67, p_method->iec_point_tbl_pop_out_cb); // ... 17个 ``` 2. **"get" 类**(12个):原本通过 `icp67_set_xxx_cb(&p_method->xxx)` 从全局表"拉取"。删除这些调用。调用方式改为直接通过 `p_icp67->method.xxx_cb()`: - 原来 `g_self_ptl.method.ao_get_cb(p_icp67, ...)` → 不变(method 中仍存储回调,但现在 icp67_init 直接把函数指针写入 p_icp67->method) - 实际上 `g_self_ptl.method` 中存储的就是从旧全局表拉取的指针。现在改为让 callers 通过 `g_self_ptl.icp67.method.ao_get_cb(...)` 调用。 3. 从 `self_ptl_init()` 删除 12 个 get 类 setter 调用,并移除 `g_self_ptl.method` 中 receiver 组字段的初始化赋值——改为直接从 `p_icp67->method` 读取。 **method.cpp**:所有通过 `p_method->xxx_cb(p_icp67, ...)` 的调用改为 `p_icp67->method.xxx_cb(p_icp67, ...)`。 --- ### T9: 更新 libcom_decode 引用 **文件**: `src/system/libcom_decode/src/decode_frame_codec.cpp` 仅包含 `myIcp67.h` 使用 `stru_head` 定义和 `icp67_show_frame()`。移除 `#include "general_method.h"` 如果存在。`icp67_show_frame()` 调用确认是否受影响(此函数在 `icp67.cpp` 中为 `LOCAL`,外部不可见——需确认正确的调用路径。若 `decode_frame_codec.cpp` 调用它,需将其改为公开)。 --- ### T10: 编译验证 ```bash ./release/build.sh ``` 确认 `libicp67.a` 构建成功、`RTU` 链接成功。 --- ## 验证方法 1. 编译:`./release/build.sh` 通过,输出 `release/x86/exe/RTU` 2. 确认无新增 warning(`-Wall -Wextra` 级别) 3. 代码审查确认: - `g_genneral_method` 全部消失 - `icp67_get_genneral_method()` 全部消失 - decode 函数全部通过 `p_icp67->method.xxx_cb` 回调 - 重传竞态已修复(字段操作在 sem 内) - 所有魔数替换为命名常量 - TI_4/TI_203 有 LOG_W + RX_FLAG 设置