/* * 模块名称: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 /* ---------------- 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; }