/* * 模块名称:IEC 60870-5-104 链路层(APCI + 状态机,纯逻辑) * 模块功能:TCP 流 → APDU 重组、U/S/I 帧编解码、STARTDT/STOPDT/TESTFR、 * k/w 流量窗与 t1/t2/t3 超时、发送序号 N(S)/N(R) 管理,以及应用层 * 流程编排:总召(act→背景扫描→actterm,首召 RBT 用 COT=4)、 * 突发上送(COV,COT=3)、周期上送(COT=1)、读单个(C_RD→COT=5)、 * 双点遥控两段式(选择/执行,失配否定确认 PN)、定值读(C_RD)/ * 定值写(C_SE_NC 选择=范围校验 / 执行=写入+封存)、时钟同步(C_CS * → clock_sync 回调)、试验(C_TS)与进程复位(C_RP,清快照)。 * **零 I/O**:收 = on_rx 喂 TCP 流片段,发 = send_apdu 回调, * 时基 = 调用方注入 now_ms(tick ms,回绕安全:差值法) * → 全栈(asdu+pts+link)主机侧纯逻辑单测可离线跑(分层约束)。 * 参数(DL/T 634.5104 标准默认,§1.5 未另定):k=12 w=8 t1=15s t2=10s t3=20s。 * 端口:TCP **2404** = 工厂默认(§1.5 B0 定义 / IEC 60870-5-104 标准端口; * 本 DTU = 被控站/服务器,主站连入)。真源与可改性见本文件下方 [comm] 段说明。 * 线程约定:单实例单任务上下文(板端 = 104 服务在 netTask 消息循环内驱动, * 见 iec104_serv 胶水层);非线程安全,禁止多任务共享一实例。 * 失败语义:send_apdu 内**不得阻塞**;发送失败由胶水层断链(主站会重连), * 本层不感知发送失败。 * 创建日期:2026-10-12(B4) */ #ifndef IEC104_LINK_H #define IEC104_LINK_H #include #include "iec104_pts.h" /* 服务端口真源已迁 [comm] 段(道G task-8):工厂默认 2404 = IEC104_COMM_DEF_PORT, * 运行期取 iec104_comm_port()(App/protocol/iec104/iec104_comm.h)。原死 define * IEC104_PORT 全仓零 .c 消费,2026-10-26 删除(旧引用见 docs/UI设计/页面扩充详细方案.md)。 */ /* 链路参数(标准默认值) */ #define IEC104_K_MAX 12U /* 未确认 I 帧上限 */ #define IEC104_W_MAX 8U /* 收到 I 帧未发确认的强制 S 阈值 */ #define IEC104_T1_MS 15000UL /* 发送/确认超时(超时 = 链路故障) */ #define IEC104_T2_MS 10000UL /* 无 I 发送时强制 S 确认超时(< t1) */ #define IEC104_T3_MS 20000UL /* 空闲发 TESTFR 周期(< t1) */ /* TCP 流重组缓冲:APDU 上限 253 + 半帧余量 */ #define IEC104_RXBUF_SZ 512U /* 单次 poll 发送预算(ASDU 数)——防总召大扫描饿死宿主任务 */ #define IEC104_POLL_BUDGET 4U /* 链路状态 */ typedef enum { IEC104_ST_IDLE = 0, /* TCP 未建立(或已断开) */ IEC104_ST_OPEN, /* TCP 已建立,等 STARTDT act */ IEC104_ST_RUN, /* STARTDT 已确认,数据服务开 */ IEC104_ST_STOPPED /* STOPDT 已确认:停数据,保 S/TESTFR */ } iec104_state_t; /* 配置与回调(ctx 原样回传给回调) */ typedef struct { uint16_t ca; /* 公共地址 = 链路地址(2B,§1.5) */ /* 发一个完整 APDU(含 6B APCI 头)。不得阻塞;生命周期仅回调内有效 */ void (*send_apdu)(void *ctx, const uint8_t *apdu, uint16_t len); /* C_CS 时钟同步:epoch 毫秒;NULL 或返回 -1 → 否定确认 */ int (*clock_sync)(void *ctx, uint64_t epoch_ms); /* 时标源(B9):当前 epoch 毫秒(M_SP_TB_1 自发用)。 * NULL 或返回 UINT64_MAX = 时钟无效 → CP56 IV 位如实置位。 * 纯读(sys_clock 推算),不得阻塞、不得有副作用。 */ uint64_t (*epoch_ms)(void *ctx); } iec104_link_cfg_t; /* 链路实例(胶水层静态持有,零堆) */ typedef struct { const iec104_link_cfg_t *cfg; void *ctx; iec104_state_t state; uint8_t init_pending; /* 1 = 下次总召按初始化(COT=4 + RBT) */ /* 序号(0..32767,帧内 ×2) */ uint16_t tx_seq; uint16_t rx_seq; uint16_t tx_unacked; /* 已发 I 未获认(k 窗) */ uint16_t rx_unacked; /* 已收 I 未确认(w 阈) */ uint8_t testfr_pending; /* 1 = TESTFR act 未获 con(t1 同样约束它) */ /* 定时器到期时刻(now_ms 域;差值法判超时,回绕安全) */ uint32_t t1_deadline; uint32_t t2_deadline; uint32_t t3_deadline; uint32_t now_ms; /* TCP 流重组 */ uint8_t rx_buf[IEC104_RXBUF_SZ]; uint16_t rx_len; /* 总召进度:0 无 / 1 背景扫描中 / 2 待发 actterm */ uint8_t interp_step; uint8_t interp_yc_idx; /* 背景扫描 YC 游标 */ uint8_t interp_yx_idx; /* 背景扫描 YX 游标 */ uint8_t interp_cot; /* 本次总召用的 COT(BG_SCAN 或 INIT) */ /* 遥控选择暂存(执行段比对) */ uint32_t yk_sel_ioa; /* 0 = 无选择 */ uint8_t yk_sel_dpc; /* 周期上送(COT=1)下次触发时刻 */ uint32_t period_next; /* COV 待发暂存(k 窗满时保留到下次 poll,变化不许丢) */ uint64_t pend_yx; uint16_t pend_yc; } iec104_link_t; /* * 函数功能:初始化实例(置 IDLE、清序号;不发送任何帧) * 入口参数:lk - 实例;cfg/ctx - 配置与回调上下文 */ void iec104_link_init(iec104_link_t *lk, const iec104_link_cfg_t *cfg, void *ctx); /* * 函数功能:TCP 连接建立(新连接或重连后调用) * 入口参数:now_ms - 当前时基 * 函数说明:序号清零、init_pending 置位(首召 = 初始化总召)、进 OPEN。 */ void iec104_link_open(iec104_link_t *lk, uint32_t now_ms); /* * 函数功能:TCP 断开(对端断开或胶水层发送失败主动断链时调用) * 函数说明:回 IDLE、清重组缓冲与选择暂存;不发送任何帧。 */ void iec104_link_close(iec104_link_t *lk); /* * 函数功能:喂入 TCP 收到的流片段(任意切分/粘帧) * 入口参数:data/len - 流片段;now_ms - 当前时基 * 函数说明:内部重组,凑齐 APDU 即处理;坏帧(起始符/长度非法)→ 视同链路 * 故障,实例回 IDLE(胶水层检测到 IDLE 跳变应断开 socket)。 * 返回后实例仍为 RUN 不代表对端合法——仅表示未触发断链条件。 */ void iec104_link_on_rx(iec104_link_t *lk, const uint8_t *data, uint16_t len, uint32_t now_ms); /* * 函数功能:周期驱动(定时器 + COV + 总召分片 + 周期上送) * 入口参数:now_ms - 当前时基(建议 ≤100 ms 调一次) * 函数说明:仅 RUN 态产生数据帧;STOPPED 态只处理 t2/t3(S/TESTFR)。 * 单次调用发送量 ≤ IEC104_POLL_BUDGET 个 ASDU。 */ void iec104_link_poll(iec104_link_t *lk, uint32_t now_ms); /* 当前状态(日志/看门狗用) */ iec104_state_t iec104_link_state(const iec104_link_t *lk); #endif /* IEC104_LINK_H */