Files
STM32F4-Base/App/protocol/iec104/iec104_link.c
T
wandering1 1d96fc9f87 feat(soe): B9 动作记录 + 时标 SOE(L4-4/L4-5)+ 104 M_SP_TB_1 自发
- 新增 App/log/soe_rec.{c,h}:64B 定长 SOE 记录纯编解码(CP56Time2a
  时标 + 类型 + 元件号 + 单调序号 + 值 + sum8 撕裂校验),零 I/O 零 OS
  依赖,主机 L0.5 覆盖(32 断言,含与 iec104 CP56 解码器交叉互证)
- 新增 App/log/soe_store.{c,h}:protectTask 边沿投递(取时标 + 组记录 +
  SPSC 待发环/消息队列,零文件系统);defaultTask 经 syslog_drain 尾部
  排空落 /log/soe.log(卷锁 try / FA_WRITE|FA_OPEN_APPEND / 校验字节数 /
  容量满 unlink 重建,全套照 run.log 实证纪律;无卷损坏归类路径)
- protect_task.c:元件动作/返回、软压板投退、采样闭锁/恢复六类边沿
  → soe_store_post;post 内禁 DBG_*(protect 静态最坏链 872 B 不变,
  改前/改后 stack_report 九任务逐字节一致),丢弃留痕挪 drain 侧
- iec104:单点 YX 自发改 M_SP_TB_1(30) 带 CP56 时标(总召/周期保持
  M_SP_NA_1,单测两种都断言);link cfg 新增 epoch_ms 时标源回调
  (NULL/无效 → IV 位如实置位);serv 绑 sys_clock(秒域时基,
  ms 域 = 秒×1000,不造假亚秒)
- 主机单测:test/host/soe/(32 断言)+ iec104 94→97(TB 时标/IV);
  iec104_master.py 解析 type 30(B6/B7 流不回归);decode_soe.py
  台架解码断言工具(seq 单调/校验和/UTC 交叉)
- 注册:uvprojx + check_registration SIM_EXEMPT 点名(MCU-only 胶水)
- 文档(我方属主区):`App/log/README.md` 增 soe 节(64B 记录/单写者/
  post 禁 DBG/UTC 交叉坑);`docs/子系统设计/IEC104上行设计说明.md`
  §1 范围行 + §3.2 时标注 + §7 台架行 + §8 第 2 条销项 + 变更记录行

台架腿(bench 锁 B9,release --bench-pass):run_boot_test PASS;
--b7-pre 21/21、--b7-post 13/13(B6/B7 回归 + 确定性动作边沿);
自发 YX 上送 M_SP_TB_1 带 ts(11:00:51 动作 / 11:00:53 复归);
FTP 下载 /log/soe.log 30 条记录 decode PASS(seq 0..28 单调、
ELM_ASSERT 14 条、UTC 交叉一致);定值/压板已恢复默认。

台账 +1088 B(SPSC 环 512 + 静态 FIL 528 + 计数器)随合并由 Lead
回填(ram_baseline --write + check_mem_numbers --write)。

gate: only mem-budget/mem-numbers pending lead ledger backfill
(pre-commit --all-files: 29 Passed / 2 Failed / 0 Skipped;
hygiene self-check OK: no BOM/CRLF/mojibake/trailws/non-ASCII literal)

sync 追平 v2-on-ui(B8 freq + N33 抬栈)后复跑:@build 0/0
(Code=209492 RW=1104 ZI=154184);stack_report 九任务全 PASS、
protectTask 872 B/57% 不变;ci_local L0.5 15 runner 951/951
(freq 46 / soe 32 / iec104 97 / prot_set 90 / prot_elm 47)。
顺带修 protect_task.c 陈旧注释:定值巡检 31 项 → 40 项(B8 后)。
2026-10-12 19:38:13 +08:00

798 lines
26 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,发成功才清位
* 函数说明:单点遥信(idx≥4)自发用 **M_SP_TB_1**(SIQ + CP56Time2a,B9:
* 变位带时标 = SOE 上送路径);双点(idx<4,V0.1 恒 IV)保持
* M_DP_NA_1;总召/周期扫描仍走 s_scan_step 的 M_SP_NA_1/M_DP_NA_1
* (时标只用于自发,标准语义——防"全量换型")。
* 时标 = cfg->epoch_ms(ctx);NULL/UINT64_MAX → CP56 IV 位如实置位。
*/
static void s_spont_step(iec104_link_t *lk, uint8_t *budget) {
uint8_t body[8]; /* 最大 = SIQ + CP56(7)(M_SP_TB_1) */
uint8_t i;
uint8_t d;
uint8_t blen;
uint8_t type;
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;
blen = 1u;
if (i < 4u) {
type = (uint8_t)IEC104_T_M_DP_NA_1;
} else {
uint64_t ems = (lk->cfg->epoch_ms != NULL)
? lk->cfg->epoch_ms(lk->ctx)
: UINT64_MAX;
type = (uint8_t)IEC104_T_M_SP_TB_1;
(void)iec104_epoch_ms_to_cp56((ems == UINT64_MAX) ? 0u : ems,
&body[1]);
if (ems == UINT64_MAX) {
body[1 + 2] |= 0x80u; /* CP56 byte2 bit7 IV:时钟无效如实上送 */
}
blen = 8u;
}
rc = s_send_asdu(lk, type, 1u, IEC104_COT_SPONT, 0u, 1u,
(uint32_t)i + 1u, body, blen, 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;
}