Files
STM32F4-Base/App/protocol/iec104/iec104_link.c
T
wandering1 90db528032 W1 功能主线完成:内存台账三件套(W1-0) + IEC104 上行(B4) + protectTask 板端接线(B5)
W1-0 台账三件套:scripts/ram_baseline.py(四模式)+ ram_baseline.tsv 趋势 +
docs/内存管理/RAM预算台账.md + pre-commit mem-budget 钩子(RW_IRAM1/RW_CCM >92%
即 FAIL + Top-10 附表);审查报告 §5.5 三条闭环。

B4 IEC 60870-5-104 上行(被控站,TCP 2404):App/protocol/iec104/ 三模块
(asdu 编解码 / pts 点表+数据源 vtable / link APCI+FSM,纯逻辑零 I/O)+
App/task/iec104_serv.c(iec104Task,NET_READY 屏障 100ms 分片喂位)。
82 断言 L0.5 全绿 + iec104-unit-test 钩子(第 31 钩子);
设计说明 docs/子系统设计/IEC104上行设计说明.md 全量回填。

B5 protectTask(L3 板端接线):App/task/protect_task.c——20ms 判据节拍
prot_elm_run + L5-4 采样故障闭锁(adc_health 电平位→元件复位)+ L5-1 定值
校验和巡检;新增 prot_elm_request_reset() 收口跨上下文复归(104 YK11 落请求、
eval 头部消费);定值/元件初始化移入 app_main_init(内核前)。TC-PE-010
(prot_elm 28 用例)。

门禁与内存:TASK_WD_PROTECT=8 入 required_mask(产品 0x77→0x1F7、HW 层
0x1F5,看门狗单测 39 断言同步);FreeRTOS 堆 48→56KB(.ioc L1 参数 +
FreeRTOSConfig.h 同步,RAM预算台账 §6 登记配额 48→56KB)。
ZI 144,008→152,204(+8,196 B = 堆 +8KB,B5 扩容);Code=204,348。
stack_report:protectTask 512 words 静态链 872 B/余 57%、iec104Task
640 words 1456 B/余 43% 均 PASS;RW_IRAM1 73.1% / RW_CCM 87.9%(<92%)。

验证:@build 0 错 0 警;ci_local 全绿(31 钩子 + 13 主机侧单测);
doc-links 135 文档 PASS。登记簿 B4/B5 销项、W1 波次完成;保护规划/看门狗
说明/AGENTS(9 任务)/RAM 三文档全部同步。

遗留(验收窗口,非代码空洞):台架第三方 104 主站往返(送检窗口)、
标准源注入动作判据、@test PROT 锚点 + protect 栈水位 + 新堆 min_ever 复测。
2026-10-12 12:36:23 +08:00

778 lines
25 KiB
C
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.
/*
* 模块名称:IEC 60870-5-104 链路层(实现)
* 模块功能:实现 iec104_link.h——APCI 流重组与 U/S/I 编解码、STARTDT/STOPDT/
* TESTFR、k/w 窗与 t1/t2/t3(差值法,tick 回绕安全)、总召分片、
* COV 突发(k 窗满暂存不丢)、周期上送、遥控/定值两段式、C_RD、
* C_CS 对时、错误应答(COT 12/13/14 + EVI)。
* 失败语义:坏帧/序号错/N(R) 越窗/缓冲溢出 → 实例置 IDLE(链路故障),
* 由胶水层检测 IDLE 跳变断开 socket;本层不感知 I/O。
* 创建日期:2026-10-12(B4)
*/
#include "iec104_link.h"
#include "iec104_asdu.h"
#include <string.h>
/* ---------------- APCI 常量 ---------------- */
#define APCI_START 0x68u
#define APCI_CTRL_LEN 4u /* len 字段最小值(纯控制域) */
#define APCI_LEN_MIN 4u
#define APCI_LEN_MAX 252u
#define SEQ_MOD 32768u /* 序号 15bit */
/* U 帧控制字节(DL/T 634.5104 表:低 2 位恒 11) */
#define U_STARTDT_ACT 0x07u
#define U_STARTDT_CON 0x0Bu
#define U_STOPDT_ACT 0x13u
#define U_STOPDT_CON 0x23u
#define U_TESTFR_ACT 0x43u
#define U_TESTFR_CON 0x83u
/* 本实现单 ASDU 信息体区上限(背景 YC 组 8×5=40 B 最大) */
#define BODY_MAX 64u
/* 周期上送周期(COT=1) */
#define PERIOD_MS 60000UL
/*
* 函数功能:超时判定(now 到达/越过 deadline,tick 回绕安全)
* 返 回 值:1 已到期 / 0 未到期
*/
static int s_expired(uint32_t now, uint32_t deadline) {
return (((int32_t)(now - deadline)) >= 0) ? 1 : 0;
}
/* ---------------- 发送原语 ---------------- */
/*
* 函数功能:发 U 帧(STARTDT/STOPDT/TESTFR act|con)
* 说明:t1 只约束"需确认的发送"(标准语义)——TESTFR act 置挂起并起 t1;
* STARTDT/STOPDT con 不启动 t1(主站可合法静默,靠 t3 探活)。
*/
static void s_send_u(iec104_link_t *lk, uint8_t q) {
uint8_t buf[6];
if (lk->cfg->send_apdu == NULL) {
return;
}
buf[0] = APCI_START;
buf[1] = APCI_CTRL_LEN;
buf[2] = q;
buf[3] = 0u;
buf[4] = 0u;
buf[5] = 0u;
lk->cfg->send_apdu(lk->ctx, buf, 6u);
/* t1 只约束"需确认的发送"(标准语义):TESTFR act 等 con;
* STARTDT/STOPDT con 不启动 t1——主站可合法静默,靠 t3 探活 */
if (q == U_TESTFR_ACT) {
lk->testfr_pending = 1u;
lk->t1_deadline = lk->now_ms + IEC104_T1_MS;
}
lk->t3_deadline = lk->now_ms + IEC104_T3_MS;
}
/*
* 函数功能:发 S 帧(确认已收序号,rx_unacked 清零)
* 说明:S 不需对端确认 → 不起 t1,只刷 t3。
*/
static void s_send_s(iec104_link_t *lk) {
uint8_t buf[6];
uint16_t r = (uint16_t)(lk->rx_seq << 1);
if (lk->cfg->send_apdu == NULL) {
return;
}
buf[0] = APCI_START;
buf[1] = APCI_CTRL_LEN;
buf[2] = 0x01u;
buf[3] = 0u;
buf[4] = (uint8_t)(r & 0xFFu);
buf[5] = (uint8_t)(r >> 8);
lk->cfg->send_apdu(lk->ctx, buf, 6u);
lk->rx_unacked = 0u;
lk->t3_deadline = lk->now_ms + IEC104_T3_MS; /* S 不需确认 → 不起 t1 */
}
/*
* 函数功能:发一个 I 帧(asdu 由调用方组好,N(S)/N(R) 由本层填)
* 返 回 值:0 已发出 / -1 k 窗满或超长(调用方保留数据下次重试)
*/
static int s_send_i(iec104_link_t *lk, const uint8_t *asdu, uint16_t len) {
uint8_t buf[6u + BODY_MAX];
uint16_t s16;
uint16_t r16;
if ((lk->cfg->send_apdu == NULL) || (len > BODY_MAX)) {
return -1;
}
if (lk->tx_unacked >= IEC104_K_MAX) {
return -1;
}
buf[0] = APCI_START;
buf[1] = (uint8_t)(APCI_CTRL_LEN + len);
s16 = (uint16_t)(lk->tx_seq << 1);
r16 = (uint16_t)(lk->rx_seq << 1);
buf[2] = (uint8_t)(s16 & 0xFFu);
buf[3] = (uint8_t)(s16 >> 8);
buf[4] = (uint8_t)(r16 & 0xFFu);
buf[5] = (uint8_t)(r16 >> 8);
(void)memcpy(&buf[6], asdu, len);
lk->tx_seq = (uint16_t)((lk->tx_seq + 1u) % SEQ_MOD);
lk->tx_unacked++;
lk->cfg->send_apdu(lk->ctx, buf, (uint16_t)(6u + len));
lk->t1_deadline = lk->now_ms + IEC104_T1_MS;
lk->t3_deadline = lk->now_ms + IEC104_T3_MS;
return 0;
}
/*
* 函数功能:组 ASDU(hdr [+ ioa] + body)并发 I 帧
* 说明:evi=1 → OA bit7(EVI,未知 IOA/类型/地址类应答)
* 返 回 值:同 s_send_i
*/
static int s_send_asdu(iec104_link_t *lk, uint8_t type, uint8_t vsq,
uint8_t cot, uint8_t cot_hi,
uint8_t with_ioa, uint32_t ioa,
const uint8_t *body, uint16_t body_len, uint8_t evi) {
uint8_t buf[6u + BODY_MAX];
uint16_t n;
n = iec104_asdu_put_hdr(buf, type, vsq, cot, cot_hi, lk->cfg->ca);
if (evi != 0u) {
buf[3] |= 0x80u; /* OA bit7 = EVI */
}
if (with_ioa != 0u) {
iec104_put_ioa(&buf[n], ioa);
n += 3u;
}
if (body_len > 0u) {
if ((uint32_t)n + (uint32_t)body_len > (uint32_t)sizeof(buf)) {
return -1;
}
(void)memcpy(&buf[n], body, body_len);
n += body_len;
}
return s_send_i(lk, buf, n);
}
/*
* 函数功能:命令应答(actcon/deactcon 等)——原样回显信息体(ioa+限定词)
* 入口参数:pn = 1 置否定确认位
*/
static void s_cmd_con(iec104_link_t *lk, uint8_t type, const uint8_t *obj,
uint16_t obj_len, uint8_t cot, uint8_t pn) {
uint8_t body[BODY_MAX];
uint16_t n = obj_len;
if (n > BODY_MAX) {
n = 0u;
}
if (n > 0u) {
(void)memcpy(body, obj, n);
}
(void)s_send_asdu(lk, type, 1u, cot,
(pn != 0u) ? IEC104_COT_BIT_PN : 0u,
0u, 0u, body, n, 0u);
}
/* ---------------- 数据上送 ---------------- */
/*
* 函数功能:单点上送遥测(M_ME_NC_1 SQ=0,值+QDS)
* 返 回 值:同 s_send_i
*/
static int s_send_yc_single(iec104_link_t *lk, uint8_t idx, uint8_t cot) {
uint8_t body[5];
float v;
uint8_t qds;
(void)iec104_pts_yc_read(idx, &v, &qds);
iec104_put_float(body, v);
body[4] = qds;
return s_send_asdu(lk, IEC104_T_M_ME_NC_1, 1u, cot, 0u, 1u,
iec104_pts_yc_ioa(idx), body, 5u, 0u);
}
/*
* 函数功能:总召/周期扫描一步(phase 游标 = interp_yx_idx:
* 0 双点组→1 单点组→2 YC 组→3 频率→4 actterm)
* 入口参数:budget = 本 poll 发送预算(成功一步减一);periodic = 周期扫描
* 返 回 值:0 本步已发出 / -1 k 窗满原地保留(poll 循环必须就此停步,
* 否则死循环——2026-10-12 单测抓到)
*/
static int s_scan_step(iec104_link_t *lk, uint8_t *budget, uint8_t periodic) {
uint8_t body[BODY_MAX];
uint8_t n = 0u;
uint8_t i;
uint8_t d;
uint8_t qds;
float v;
int rc = -1;
switch (lk->interp_yx_idx) {
case 0u: /* YX 双点组 SQ=1,IOA 1..4 */
for (i = 0u; i < 4u; i++) {
iec104_pts_yx_read(i, &d);
body[n++] = d;
}
rc = s_send_asdu(lk, IEC104_T_M_DP_NA_1, (uint8_t)(IEC104_VSQ_SQ | 4u),
lk->interp_cot, 0u, 1u, 1u, body, n, 0u);
break;
case 1u: /* YX 单点组 SQ=1,IOA 5..33 */
for (i = 4u; i < 33u; i++) {
iec104_pts_yx_read(i, &d);
body[n++] = d;
}
rc = s_send_asdu(lk, IEC104_T_M_SP_NA_1, (uint8_t)(IEC104_VSQ_SQ | 29u),
lk->interp_cot, 0u, 1u, 5u, body, n, 0u);
break;
case 2u: /* YC 组 SQ=1,IOA 0x4011..0x4018 */
for (i = 0u; i < 8u; i++) {
(void)iec104_pts_yc_read(i, &v, &qds);
iec104_put_float(&body[n], v);
n += 4u;
body[n++] = qds;
}
rc = s_send_asdu(lk, IEC104_T_M_ME_NC_1, (uint8_t)(IEC104_VSQ_SQ | 8u),
lk->interp_cot, 0u, 1u, 0x4011u, body, n, 0u);
break;
case 3u: /* 频率单点 SQ=0,IOA 0x401D */
(void)iec104_pts_yc_read(8u, &v, &qds);
iec104_put_float(&body[n], v);
n += 4u;
body[n++] = qds;
rc = s_send_asdu(lk, IEC104_T_M_ME_NC_1, 1u, lk->interp_cot, 0u,
1u, 0x401Du, body, n, 0u);
break;
default: /* actterm(仅总召;周期扫描不走这步) */
iec104_put_ioa(&body[n], 0u);
n += 3u;
body[n++] = IEC104_RQT_GROUP1;
rc = s_send_asdu(lk, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACTTERM, 0u,
0u, 0u, body, n, 0u);
break;
}
if (rc != 0) {
return -1; /* k 窗满:下次 poll 重试本 phase */
}
(*budget)--;
lk->interp_yx_idx++;
if (periodic != 0u) {
if (lk->interp_yx_idx >= 4u) {
lk->interp_step = 0u; /* 周期只发 YC(phase 2..3),无 actterm */
}
} else if (lk->interp_yx_idx >= 5u) {
lk->interp_step = 0u;
lk->init_pending = 0u;
}
return 0;
}
/*
* 函数功能:突发(COV)上送——pend 暂存逐点 SQ=0 COT=3,发成功才清位
*/
static void s_spont_step(iec104_link_t *lk, uint8_t *budget) {
uint8_t body[5];
uint8_t i;
uint8_t d;
int rc;
while ((*budget > 0u) && ((lk->pend_yx != 0u) || (lk->pend_yc != 0u))) {
if (lk->pend_yx != 0u) {
i = 0u;
while (i < 33u) {
if ((lk->pend_yx & (1uLL << i)) != 0u) {
break;
}
i++;
}
iec104_pts_yx_read(i, &d);
body[0] = d;
rc = s_send_asdu(lk,
(i < 4u) ? IEC104_T_M_DP_NA_1
: IEC104_T_M_SP_NA_1,
1u, IEC104_COT_SPONT, 0u, 1u,
(uint32_t)i + 1u, body, 1u, 0u);
if (rc != 0) {
return; /* k 窗满:pend 位保留,下次续发 */
}
lk->pend_yx &= ~(1uLL << i);
} else {
i = 0u;
while (i < 16u) {
if ((lk->pend_yc & (uint16_t)(1u << i)) != 0u) {
break;
}
i++;
}
rc = s_send_yc_single(lk, i, IEC104_COT_SPONT);
if (rc != 0) {
return;
}
lk->pend_yc &= (uint16_t)~(1u << i);
}
(*budget)--;
}
}
/* ---------------- 命令处理 ---------------- */
/*
* 函数功能:C_IC_NA_1 总召处理(ACT → actcon + 启动扫描;DEACT → deactcon)
* 说明:RBT(bit4)落在 COT 6bit 值域内,ACT+RBT 上收为 22——先剥再判;
* 首召/RBT → COT=4(init),后续 COT=2(bg)
*/
static void s_h_ic(iec104_link_t *lk, const iec104_asdu_hdr_t *h) {
/* RBT(bit4)落在 COT 6bit 值域内:ACT+RBT 上收为 22——先剥 RBT 再判值 */
uint8_t cot_val = (uint8_t)(h->cot & 0xEFu);
if (cot_val == IEC104_COT_DEACT) {
lk->interp_step = 0u;
s_cmd_con(lk, IEC104_T_C_IC_NA_1, h->obj, h->obj_len,
IEC104_COT_DEACTCON, 0u);
return;
}
if (cot_val != IEC104_COT_ACT) {
return;
}
if ((h->obj_len < 4u) || (h->obj[3] != IEC104_RQT_GROUP1)) {
s_cmd_con(lk, IEC104_T_C_IC_NA_1, h->obj, h->obj_len,
IEC104_COT_ACTCON, 1u);
return;
}
s_cmd_con(lk, IEC104_T_C_IC_NA_1, h->obj, h->obj_len, IEC104_COT_ACTCON, 0u);
lk->interp_step = 1u;
lk->interp_yx_idx = 0u;
lk->interp_cot = ((lk->init_pending != 0u)
|| ((h->cot & IEC104_COT_BIT_RBT) != 0u))
? (uint8_t)IEC104_COT_INIT : (uint8_t)IEC104_COT_BG_SCAN;
}
/*
* 函数功能:C_CS_NA_1 对时(CP56 → epoch → cfg->clock_sync)
* 说明:IV/字段坏/系统侧拒绝 → actcon + PN
*/
static void s_h_cs(iec104_link_t *lk, const iec104_asdu_hdr_t *h) {
uint64_t epoch = 0u;
int rc = -1;
if (h->obj_len >= 10u) {
rc = iec104_cp56_to_epoch_ms(&h->obj[3], &epoch);
if ((rc == 0) && (lk->cfg->clock_sync != NULL)) {
rc = lk->cfg->clock_sync(lk->ctx, epoch);
}
}
s_cmd_con(lk, IEC104_T_C_CS_NA_1, h->obj, h->obj_len, IEC104_COT_ACTCON,
(rc == 0) ? 0u : 1u);
}
/*
* 函数功能:C_DC_NA_1 遥控两段式(选择记 ioa+dpc;执行须匹配,否则 PN)
*/
static void s_h_dc(iec104_link_t *lk, const iec104_asdu_hdr_t *h) {
uint32_t ioa;
uint8_t qdc;
uint8_t sel;
uint8_t dpc;
int rc;
if (h->obj_len < 4u) {
return;
}
ioa = iec104_get_ioa(h->obj);
qdc = h->obj[3];
sel = (uint8_t)(((qdc & IEC104_QDC_S) != 0u) ? 1u : 0u);
dpc = (uint8_t)(qdc & 0x03u);
if (sel != 0u) {
rc = iec104_pts_yk_exec(ioa, dpc, 1u);
if (rc == 0) {
lk->yk_sel_ioa = ioa;
lk->yk_sel_dpc = dpc;
}
} else {
if ((lk->yk_sel_ioa == ioa) && (lk->yk_sel_dpc == dpc)) {
rc = iec104_pts_yk_exec(ioa, dpc, 0u);
} else {
rc = -1; /* 无选择/失配 → 否定确认 */
}
lk->yk_sel_ioa = 0u;
lk->yk_sel_dpc = 0u;
}
s_cmd_con(lk, IEC104_T_C_DC_NA_1, h->obj, h->obj_len, IEC104_COT_ACTCON,
(rc == 0) ? 0u : 1u);
}
/*
* 函数功能:C_SE_NC_1 定值两段式写(选择段只校验;执行段落盘)
* 说明:越界/未知地址 → actcon + PN
*/
static void s_h_se(iec104_link_t *lk, const iec104_asdu_hdr_t *h) {
uint32_t addr;
float v;
uint8_t sel;
int rc;
if (h->obj_len < 8u) {
return;
}
addr = iec104_get_ioa(h->obj);
v = iec104_get_float(&h->obj[3]);
sel = (uint8_t)(((h->obj[7] & IEC104_QDC_S) != 0u) ? 1u : 0u);
rc = iec104_pts_set_write(addr, v, sel);
s_cmd_con(lk, IEC104_T_C_SE_NC_1, h->obj, h->obj_len, IEC104_COT_ACTCON,
(rc == 0) ? 0u : 1u);
}
/*
* 函数功能:C_RD_NA_1 读单个信息体(YC 命中 → M_ME COT5;定值命中 → 同;
* 未知 IOA → 原样回 C_RD + COT14 + EVI)
*/
static void s_h_rd(iec104_link_t *lk, const iec104_asdu_hdr_t *h) {
uint32_t ioa;
uint8_t i;
float v;
if (h->obj_len < 3u) {
return;
}
ioa = iec104_get_ioa(h->obj);
for (i = 0u; i < iec104_pts_yc_count(); i++) {
if (iec104_pts_yc_ioa(i) == ioa) {
s_send_yc_single(lk, i, IEC104_COT_REQ);
return;
}
}
if (iec104_pts_set_read(ioa, &v) == 0) {
uint8_t body[5];
iec104_put_float(body, v);
body[4] = 0u;
(void)s_send_asdu(lk, IEC104_T_M_ME_NC_1, 1u, IEC104_COT_REQ, 0u,
1u, ioa, body, 5u, 0u);
return;
}
(void)s_send_asdu(lk, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_UNK_IOA, 0u,
1u, ioa, NULL, 0u, 1u);
}
/*
* 函数功能:ASDU 分派(公共地址校验 → 类型路由)
* 说明:错 ca → COT13+EVI;未知类型 → COT12+EVI(原类型回)
*/
static void s_h_asdu(iec104_link_t *lk, const uint8_t *asdu, uint16_t len) {
iec104_asdu_hdr_t h;
if (iec104_asdu_parse_hdr(asdu, len, &h) != 0) {
return;
}
if (h.ca != lk->cfg->ca) {
(void)s_send_asdu(lk, h.type, 1u, IEC104_COT_UNK_CA, 0u,
0u, 0u, NULL, 0u, 1u);
return;
}
switch (h.type) {
case IEC104_T_C_IC_NA_1:
s_h_ic(lk, &h);
break;
case IEC104_T_C_CS_NA_1:
s_h_cs(lk, &h);
break;
case IEC104_T_C_DC_NA_1:
s_h_dc(lk, &h);
break;
case IEC104_T_C_SE_NC_1:
s_h_se(lk, &h);
break;
case IEC104_T_C_RD_NA_1:
s_h_rd(lk, &h);
break;
case IEC104_T_C_TS_NA_1:
s_cmd_con(lk, IEC104_T_C_TS_NA_1, h.obj, h.obj_len,
IEC104_COT_ACTCON, 0u);
break;
case IEC104_T_C_RP_NA_1:
iec104_pts_cov_check(NULL); /* 进程复位:重建快照(清历史) */
s_cmd_con(lk, IEC104_T_C_RP_NA_1, h.obj, h.obj_len,
IEC104_COT_ACTCON, 0u);
break;
default:
(void)s_send_asdu(lk, h.type, 1u, IEC104_COT_UNK_COT, 0u,
0u, 0u, NULL, 0u, 1u);
break;
}
}
/* ---------------- APDU 分派 ---------------- */
/*
* 函数功能:U 帧处理(STARTDT/STOPDT act → con + 态迁移;TESTFR act → con;
* TESTFR con → 清挂起)
*/
static void s_h_u(iec104_link_t *lk, uint8_t q) {
switch (q) {
case U_STARTDT_ACT:
s_send_u(lk, U_STARTDT_CON);
lk->state = IEC104_ST_RUN;
lk->interp_step = 0u;
lk->yk_sel_ioa = 0u;
break;
case U_STOPDT_ACT:
s_send_u(lk, U_STOPDT_CON);
lk->state = IEC104_ST_STOPPED;
lk->interp_step = 0u;
break;
case U_TESTFR_ACT:
s_send_u(lk, U_TESTFR_CON);
break;
case U_TESTFR_CON:
lk->testfr_pending = 0u;
break;
default:
break; /* con/未知 U:t1/t3 已由外层刷新 */
}
}
/*
* 函数功能:N(R) 校验并更新 tx_unacked(V(A) ≤ V(A)' ≤ V(S),即 0 ≤ d ≤ 窗内)
* 说明:N(R) 倒退(早于上次确认)= 协议违例 → 链路故障(标准语义);
* tx_unacked 恒 ≤ k(发送侧门控),故 d ≤ tx_unacked 已蕴含窗校验
* 返 回 值:0 / -1 违例
*/
static int s_apply_nr(iec104_link_t *lk, uint16_t nr) {
uint16_t d = (uint16_t)(((uint32_t)lk->tx_seq + SEQ_MOD - (uint32_t)nr)
% SEQ_MOD);
if ((uint32_t)d > (uint32_t)lk->tx_unacked) {
return -1;
}
lk->tx_unacked = d;
return 0;
}
/*
* 函数功能:单个完整 APDU 分派(U/S/I);任何接收只刷 t3(不刷 t1)
* 说明:OPEN 态只认 U;I 帧 N(S) 不连续 → IDLE;RUN 态才处理 ASDU;
* 收满 w 个未确认 → 回 S
*/
static void s_h_apdu(iec104_link_t *lk, const uint8_t *p, uint16_t total) {
const uint8_t *c = &p[2]; /* 控制域 4B */
uint16_t ns;
uint16_t nr;
lk->t3_deadline = lk->now_ms + IEC104_T3_MS;
if ((c[0] & 0x03u) == 0x03u) {
s_h_u(lk, c[0]);
return;
}
if (lk->state == IEC104_ST_OPEN) {
return; /* OPEN 态只认 U 帧 */
}
if ((c[0] & 0x01u) != 0u) { /* S 格式 */
nr = (uint16_t)(((uint16_t)c[2] | ((uint16_t)c[3] << 8)) >> 1);
if (s_apply_nr(lk, nr) != 0) {
lk->state = IEC104_ST_IDLE;
}
return;
}
/* I 格式 */
ns = (uint16_t)(((uint16_t)c[0] | ((uint16_t)c[1] << 8)) >> 1);
nr = (uint16_t)(((uint16_t)c[2] | ((uint16_t)c[3] << 8)) >> 1);
if (ns != lk->rx_seq) {
lk->state = IEC104_ST_IDLE; /* 序号错 → 链路故障 */
return;
}
if (s_apply_nr(lk, nr) != 0) {
lk->state = IEC104_ST_IDLE;
return;
}
lk->rx_seq = (uint16_t)((ns + 1u) % SEQ_MOD);
lk->rx_unacked++;
if (lk->rx_unacked == 1u) {
lk->t2_deadline = lk->now_ms + IEC104_T2_MS;
}
if (lk->state == IEC104_ST_RUN) {
s_h_asdu(lk, &p[6], (uint16_t)(total - 6u));
}
if (lk->rx_unacked >= IEC104_W_MAX) {
s_send_s(lk);
}
}
/* ---------------- 公开接口 ---------------- */
/*
* 函数功能:链路实例初始化(全清零 + 绑配置与上下文,态 = IDLE)
*/
void iec104_link_init(iec104_link_t *lk, const iec104_link_cfg_t *cfg,
void *ctx) {
(void)memset(lk, 0, sizeof(*lk));
lk->cfg = cfg;
lk->ctx = ctx;
lk->state = IEC104_ST_IDLE;
}
/*
* 函数功能:TCP 连接建立 → OPEN(序号/窗口/定时/暂存全部复位,init_pending=1)
*/
void iec104_link_open(iec104_link_t *lk, uint32_t now_ms) {
lk->state = IEC104_ST_OPEN;
lk->tx_seq = 0u;
lk->rx_seq = 0u;
lk->tx_unacked = 0u;
lk->rx_unacked = 0u;
lk->testfr_pending = 0u;
lk->init_pending = 1u;
lk->rx_len = 0u;
lk->yk_sel_ioa = 0u;
lk->yk_sel_dpc = 0u;
lk->interp_step = 0u;
lk->interp_yx_idx = 0u;
lk->pend_yx = 0u;
lk->pend_yc = 0u;
lk->now_ms = now_ms;
lk->t1_deadline = now_ms + IEC104_T1_MS;
lk->t2_deadline = now_ms + IEC104_T2_MS;
lk->t3_deadline = now_ms + IEC104_T3_MS;
lk->period_next = now_ms + PERIOD_MS;
}
/*
* 函数功能:链路断开/故障 → IDLE(清重组缓冲与所有暂存)
*/
void iec104_link_close(iec104_link_t *lk) {
lk->state = IEC104_ST_IDLE;
lk->rx_len = 0u;
lk->interp_step = 0u;
lk->pend_yx = 0u;
lk->pend_yc = 0u;
lk->yk_sel_ioa = 0u;
lk->yk_sel_dpc = 0u;
}
/*
* 函数功能:喂入收到的原始字节(APCI 流重组:半帧/粘帧逐处)
* 说明:坏起始符/坏长度/缓冲溢出 → IDLE(胶水层检测后断开 socket)
*/
void iec104_link_on_rx(iec104_link_t *lk, const uint8_t *data, uint16_t len,
uint32_t now_ms) {
lk->now_ms = now_ms;
if ((lk->state == IEC104_ST_IDLE) || (data == NULL)) {
return;
}
if ((uint32_t)len > (uint32_t)(IEC104_RXBUF_SZ - lk->rx_len)) {
lk->state = IEC104_ST_IDLE; /* 重组缓冲溢出 = 坏流 */
lk->rx_len = 0u;
return;
}
(void)memcpy(&lk->rx_buf[lk->rx_len], data, len);
lk->rx_len = (uint16_t)(lk->rx_len + len);
while (lk->rx_len >= 2u) {
uint16_t lf;
uint16_t need;
if (lk->rx_buf[0] != APCI_START) {
lk->state = IEC104_ST_IDLE;
break;
}
lf = lk->rx_buf[1];
if ((lf < APCI_LEN_MIN) || (lf > APCI_LEN_MAX)) {
lk->state = IEC104_ST_IDLE;
break;
}
need = (uint16_t)(lf + 2u);
if (lk->rx_len < need) {
break; /* 半帧:等下次 on_rx */
}
s_h_apdu(lk, lk->rx_buf, need);
if (lk->state == IEC104_ST_IDLE) {
break;
}
if (need < lk->rx_len) {
(void)memmove(lk->rx_buf, &lk->rx_buf[need],
(uint16_t)(lk->rx_len - need));
}
lk->rx_len = (uint16_t)(lk->rx_len - need);
}
if (lk->state == IEC104_ST_IDLE) {
lk->rx_len = 0u;
}
}
/*
* 函数功能:周期节拍(胶水层每次服务循环调用一次,时基由调用方注入)
* 说明:t1 超时 → close;t3 到期 → TESTFR act;t2 到期 → S;
* RUN 态按优先级:总召续扫 > 周期续扫 > COV 突发 > 周期触发
*/
void iec104_link_poll(iec104_link_t *lk, uint32_t now_ms) {
uint8_t budget = IEC104_POLL_BUDGET;
lk->now_ms = now_ms;
if ((lk->state != IEC104_ST_RUN) && (lk->state != IEC104_ST_STOPPED)) {
return;
}
if (((lk->tx_unacked > 0u) || (lk->testfr_pending != 0u))
&& (s_expired(now_ms, lk->t1_deadline) != 0)) {
iec104_link_close(lk); /* t1:未确认 I/未答 TESTFR 超时 = 故障 */
return;
}
if (s_expired(now_ms, lk->t3_deadline) != 0) {
s_send_u(lk, U_TESTFR_ACT);
}
if ((lk->rx_unacked > 0u) && (s_expired(now_ms, lk->t2_deadline) != 0)) {
s_send_s(lk);
}
if (lk->state != IEC104_ST_RUN) {
return;
}
if (lk->interp_step == 1u) {
while ((budget > 0u) && (lk->interp_step == 1u)) {
if (s_scan_step(lk, &budget, 0u) != 0) {
break; /* k 窗满:本 poll 停步(防死循环) */
}
}
return;
}
if (lk->interp_step == 3u) {
while ((budget > 0u) && (lk->interp_step == 3u)) {
if (s_scan_step(lk, &budget, 1u) != 0) {
break;
}
}
return;
}
if ((lk->pend_yx != 0u) || (lk->pend_yc != 0u)) {
s_spont_step(lk, &budget);
if (budget == 0u) {
return;
}
}
if (s_expired(now_ms, lk->period_next) != 0) {
lk->interp_step = 3u;
lk->interp_yx_idx = 2u; /* 周期只扫 YC(phase 2..3) */
lk->interp_cot = IEC104_COT_PERIODIC;
lk->period_next = now_ms + PERIOD_MS;
while ((budget > 0u) && (lk->interp_step == 3u)) {
if (s_scan_step(lk, &budget, 1u) != 0) {
break;
}
}
return;
}
{
iec104_pts_cov_t cov;
iec104_pts_cov_check(&cov);
lk->pend_yx |= cov.yx_changed;
lk->pend_yc |= cov.yc_changed;
s_spont_step(lk, &budget);
}
}
/*
* 函数功能:查询链路状态(胶水层检测 IDLE 跳变以断开/重建 socket)
*/
iec104_state_t iec104_link_state(const iec104_link_t *lk) {
return lk->state;
}