# libicp67 — ICP67 自定义协议库 API 参考

## 模块概述

**位置**: `src/protocol/libicp67/`

**定位**: ICP67 二进制协议引擎 — 负责帧定界、编解码、重传管理。纯 C 实现，extern "C" 接口。

**依赖**: `liblog` (日志), POSIX `sem_t` (信号量)

**被依赖**: `libself_ptl` (自协议应用层)

## 协议帧格式

```
| 0x67 | len(2B) | 0x67 | src | dst | dir | ti | cot | info_addr(2B) | dev_addr(4B) | data... | CRC(1B) | 0x16 |
| 帧头1 |  长度   | 帧头2 |  源 | 目的 | 方向 |类型| 原因 |   信息体地址    |   设备地址    |  数据体  |   校验和  |  帧尾  |
```

- **帧头**: 0x67 0x67 (双字节定界)
- **校验**: 8-bit 累加和 (CRC8)
- **帧尾**: 0x16
- **设备地址**: src/dst 取值 0=维护软件, 1=主板, 2=主板, 3=采样板, 4=LCD

## 架构

```
libself_ptl (应用层)
    │
    ├─ icp67_init() 初始化
    ├─ icp67_set_*_cb() 注册 17 个回调
    │
    ▼
libicp67 (协议引擎)
    ├─ icp67_search_frame() 帧定界
    ├─ icp67_decode() 解码分发
    ├─ icp67_timer_handler() 重传管理
    ├─ Sender 函数 (13个) 组帧发送
    └─ 16 种 TI 解码函数
```

## 文件清单

| 文件 | 行数 | 说明 |
|---|---|---|
| `release/inc/myIcp67.h` | 331 | 公共契约: 帧结构、回调类型、`stru_icp67`、常量宏 |
| `src/protocol/libicp67/inc/icp67.h` | ~500 | 内部头: TI/COT枚举、ASDU结构体、内部API声明 |
| `src/protocol/libicp67/src/icp67_frame.c` | 126 | 帧定界、CRC校验、帧构造、调试打印 |
| `src/protocol/libicp67/src/icp67_decode.c` | 1024 | 16种TI解码+文件/ZIP子命令分发 |
| `src/protocol/libicp67/src/icp67_core.c` | 645 | 初始化、回调注册、重传管理、Sender函数、定时器 |

## 对外 API

### 初始化

```c
void icp67_init(stru_icp67 *p_icp67, icp67_send_cb send_cb, void *arg);
```

初始化协议实例，绑定发送回调和用户参数。内部自动绑定:
- `search_frame_cb` → `icp67_search_frame()`
- `decode_cb` → `icp67_decode()`
- `timer_handler_cb` → `icp67_timer_handler()`
- 创建重传信号量 `rtx_sem`
- 初始化 13 个 Sender 回调为内部函数
- 设置超时参数 `tm_out = 1000ms / 10ms = 100 tick`

```c
int icp67_set_ao_cfg_md5(stru_icp67 *p_icp67, uint8_t *p_md5);
```

设置 AO 参数配置的 MD5 校验值 (16字节)，用于后续 TI_1/TI_2 帧的 MD5 验证。

### 回调注册 (17 个 setter)

**pop_out 类 (3 个)** — 解码后回调注册:
| 函数 | 注册回调 | 触发场景 |
|---|---|---|
| `icp67_set_ao_pop_out_cb` | `ao_pop_out_cb` | TI_1 参数读取响应 |
| `icp67_set_iec_point_tbl_pop_out_cb` | `iec_point_tbl_pop_out_cb` | TI_3 点表读取响应 |
| `icp67_set_self_check_pop_out_cb` | `self_check_pop_out_cb` | TI_6 自检响应 |

**文件操作类 (6 个):**
| 函数 | 注册回调 | 触发场景 |
|---|---|---|
| `icp67_set_dir_pop_out_cb` | `dir_pop_out_cb` | TI_11_2 目录响应 |
| `icp67_set_file_read_act_confirm_cb` | `file_read_act_confirm_cb` | TI_11_4 读文件激活响应 |
| `icp67_set_file_read_cb` | `file_read_cb` | TI_11_5 读文件内容 |
| `icp67_set_file_write_act_confirm_cb` | `file_write_act_confirm_cb` | TI_11_8 写文件激活响应 |
| `icp67_set_file_write_confirm_cb` | `file_write_confirm_cb` | TI_11_10 写文件确认 |
| `icp67_set_file_write_end_cb` | `file_write_end_cb` | TI_11_10 写文件结束 |

**ZIP 类 (2 个):**
| 函数 | 注册回调 | 触发场景 |
|---|---|---|
| `icp67_set_zip_dir_pop_out_cb` | `zip_dir_pop_out_cb` | TI_30_1 录波目录 |
| `icp67_set_zip_file_pop_out_cb` | `zip_file_pop_out_cb` | TI_30_3 录波文件内容 |

**数据传输类 (6 个):**
| 函数 | 注册回调 | 触发场景 |
|---|---|---|
| `icp67_set_mx_trans_cb` | `mx_trans_cb` | TI_200 遥测传输 |
| `icp67_set_st_trans_cb` | `st_trans_cb` | TI_201 遥信传输 |
| `icp67_set_dd_trans_cb` | `dd_trans_cb` | TI_202 电度传输 |
| `icp67_set_soe_trans_cb` | `soe_trans_cb` | TI_204 SOE 事件 |
| `icp67_set_fault_trans_cb` | `fault_trans_cb` | TI_205 故障事件 |
| `icp67_set_mx_change_cb` | `mx_change_cb` | TI_207 遥测扰动 |

### 帧操作 (内部 API)

```c
int  icp67_search_frame(uint8_t *p_data, uint16_t len, uint16_t *p_pos, uint16_t *p_valid_len);
int  icp67_decode(stru_icp67 *p_icp67, uint8_t *p_data, uint16_t len);
void icp67_timer_handler(stru_icp67 *p_icp67);
void icp67_make_head(uint8_t dst, uint8_t ti, uint8_t cot, uint16_t inf, uint8_t *p_tx, uint16_t *tx_len);
void icp67_make_tail(uint8_t *p_tx, uint16_t *tx_len);
void icp67_show_frame(uint8_t *p_data, uint16_t len);
```

### 重传管理 (线程安全)

```c
void icp67_rtx_flag_set(stru_icp67 *p_icp67, uint8_t flag, uint8_t resend_cnt, uint32_t tm_cnt);
void icp67_rtx_flag_get(stru_icp67 *p_icp67, uint8_t *flag, uint8_t *resend_cnt, uint32_t *tm_cnt);
void icp67_rtx_resend_inc(stru_icp67 *p_icp67);
void icp67_rtx_tm_inc(stru_icp67 *p_icp67);
```

所有读写操作通过 `rtx_sem` 信号量保护。

## 核心数据结构

### stru_icp67 — 协议实例

```c
typedef struct _icp67 {
    void                  *arg;              // 用户数据 (interface ID)
    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;           // 回调表 (实例化)
    uint8_t                md5[16];          // AO参数MD5
    uint8_t                tx[2048];         // 发送缓冲区
    uint8_t                resend_tx[2048];  // 重发备份
    uint8_t                rtx_flag;         // 重传标志
    sem_t                  rtx_sem;          // 重传信号量
    uint8_t                resend_cnt;       // 重发计数
    uint32_t               tm_out;           // 超时阈值
    uint32_t               tm_cnt;           // 当前tick
} icp67;
```

### stru_genneral_method — 回调表 (29 个函数指针)

- **Sender 组** (13个): 方向 self_ptl → icp67 → 物理层
  - ao_get, ao_set, iec_point_tbl_get, time_set, self_check_get
  - upgrade_start, dir_read, file_read_act, file_write_act
  - file_write, file_read_confirm, file_write_end_confirm

- **Receiver 组** (16个): 方向 物理层 → icp67 → self_ptl
  - ao_pop_out, iec_point_tbl_pop_out, self_check_pop_out
  - dir_pop_out, file_read_act_confirm, file_read
  - file_write_act_confirm, file_write_confirm, file_write_end
  - zip_dir_pop_out, zip_file_pop_out
  - mx_trans, st_trans, dd_trans, soe_trans, fault_trans, mx_change

## TI 消息类型清单

| TI | 名称 | COT | 方向 | 说明 |
|:--:|---|---|---|---|
| 1 | 读独立编码参数 | 5→7 | 请求→响应 | 带 MD5 校验的参数读取 |
| 2 | 写独立编码参数 | 6→7 | 激活→确认 | 参数写入，含 MD5 |
| 3 | 读组合编码参数 | 5→7 | 请求→响应 | 点表/校准数据，支持分帧 |
| 4 | 写组合编码参数 | - | - | 空桩 (未使用) |
| 5 | 对时 | 5→7 | 请求→响应 | 终端时间同步 |
| 6 | 自检读取 | 5→7 | 请求→响应 | 终端状态/版本号 |
| 9 | 升级 | 5→7 | 请求→响应 | 固件升级 |
| 11 | 文件操作 | 5→7 | 请求→响应 | 目录/文件读写, 9 种子命令 |
| 30 | 录波文件 | 5 | 上行 | ZIP 波形目录/文件传输 |
| 200 | 遥测传输 | 6 | 上行 | 采样板→主板 MX |
| 201 | 遥信传输 | 6 | 上行 | 采样板→主板 ST |
| 202 | 计量传输 | 6 | 上行 | 采样板→主板 DD |
| 203 | 遥控交互 | - | - | 空桩 (未使用) |
| 204 | SOE 传输 | 3 | 上行 | 事件顺序记录 |
| 205 | 故障传输 | 3 | 上行 | 故障事件 (含 ST+MX) |
| 207 | 扰动传输 | 3 | 上行 | 遥测扰动数据 |

## 与原工程的差异

| 维度 | 原 RTU (icp67.cpp) | 重构后 (纯C 4文件) |
|---|---|---|
| 回调表 | 全局 `g_genneral_method` | 实例化 `p_icp67->method` |
| TI 映射 | C++ `std::map` | C 数组 + 线性查表 |
| 故障解码 | `std::vector` | 固定数组 `[MAX_FAULT_*_INFO]` |
| 文件格式 | `icp67.cpp` (1429行) + `general_method.cpp` (88行) | 4 文件: frame(126) + decode(1024) + core(645) + header(~830) |
| 语言 | C++ | 纯 C (extern "C" 接口) |
| 常量 | 魔数 | 命名 `#define` (ICP67_TX_BUF_SIZE 等) |
| 空桩 | TI_4/TI_203 空 | LOG_E + RX_FLAG 设置 |

## 完成状态

### ✅ 已完成

- [x] 帧定界/校验 (search_frame, CRC)
- [x] 16 种 TI 解码 (含文件/ZIP 子命令)
- [x] 13 个 Sender 函数 (组帧发送)
- [x] 17 个回调 setter (实例化注册)
- [x] 重传管理 (4 函数, sem 线程安全)
- [x] 定时器处理 (10ms tick, 5 次重试)
- [x] TI_4/TI_203 空桩填充
- [x] 多实例隔离 (回调表实例化)
- [x] 魔数 → 命名常量
- [x] C 数组查表替代 std::map
- [x] 单元测试 (21 项全通过)

### ⬜ 未完成

- [ ] TI_203 遥控交互实现 (FTU 固件不支持, 暂缓)

> 以下两项因 FTU 固件不可改动, 已删除:
> ~~CRC 升级 (需 FTU 同步改帧格式)~~
> ~~帧序号/去重 (需 FTU 同步改帧格式)~~

## 新增功能 (2026-07-09)

### 可配置超时/重试

```c
// 旧
void icp67_init(stru_icp67 *p, icp67_send_cb cb, void *arg);

// 新: 传 0 使用默认值 (1000ms, 5次)
void icp67_init(stru_icp67 *p, icp67_send_cb cb, void *arg,
                uint32_t tm_out_ms, uint8_t resend_max);

// 示例
icp67_init(&icp, send_cb, &iface, 2000, 3);  // 串口慢速: 2s超时, 重试3次
icp67_init(&icp, send_cb, &iface, 0, 0);     // 使用默认值
```

`stru_icp67` 新增字段: `resend_max`, `tm_out_ms`。

### 零拷贝环形缓冲区搜索

```c
int icp67_search_frame_in_ring(const uint8_t *buf, uint16_t size,
    uint16_t rptr, uint16_t cnt,
    uint16_t *p_pos, uint16_t *p_valid_len);
```

直接从环形缓冲区读指针扫描帧, 省去 `get_rx_data()` 的 2048 字节 `memcpy`。
`self_ptl` 已接入, 仅 IEC-104 检测保留一次全量拷贝。

## 测试覆盖

| 测试项 | 覆盖内容 | 状态 |
|---|---|---|
| T1 帧构造 | make_head + make_tail | ✅ |
| T2 帧搜索 | 正常帧、无效帧头、NULL参数 | ✅ |
| T3 CRC | 基本累加、溢出回绕 | ✅ |
| T4 初始化 | 绑定回调、信号量、超时参数 | ✅ |
| T5 重传管理 | set/get、resend_inc | ✅ |
| T6 回调注册 | 17 个 setter 全路径 | ✅ |
| T7 解码无效 | NULL参数 | ✅ |
| T8 定时器 | NULL、空闲态 | ✅ |
| T9 MD5 | 正常设置、NULL参数 | ✅ |
| T10 未知 TI | 0xFF 帧 | ✅ |
| T11 Sender | self_check_get、iec_point_tbl_get、NULL安全 | ✅ |
| T12 解码 TI | TI_1 MD5 匹配、TI_4 空桩 | ✅ |
| T13 多实例 | 实例间回调隔离 | ✅ |
