e4d70f9fcb
证据源 = 主树 ci-artifacts/archify/architecture-dtu-overview-20261030-095132/ evidence.md §11。该目录不入库,故把每条的判据与实测源抄进正文,别让证据只活在 一台机器上。处置口径:能改的改成与代码同源,核实后确认文档没问题的明确写「不改」。 判停点与就绪屏障(条 1、条 3) - AGENTS 原写 `protect_task.c:98` 直接停走判据:`:98` 是 `s_prev_cal_lock` 变量, 判停点是 `App/task/protect_task.c:268 if (adc_cal_prot_blocked() != 0u)`, 判定本体在标定层 `adc_cal_prot_blocked()`、protectTask 只是消费者 → 改引用, 同步 `ui_task.c:240` 页面注释与 `docs/任务规划/数据可信.md` 的同一处行号。 - `SYS_EVT_FTP_READY` 有生产者(`Src/freertos.c:490`,注释自陈预留)无消费者; `SYS_EVT_TIMESYNC_READY` 全仓无生产者也无消费者(只有定义与测试桩)。 AGENTS、`sys_events.h` 位注释、屏障配图(生成器 + 产物)三处从「四个就绪位 并列」改成「bit0/bit1 在用 + bit2/bit3 预留」,并指向 `docs/架构设计/项目启动顺序管理.md` §3.2 的逐位对照表。ftpTask 实际等的是 `NET|STORAGE`(`Src/freertos.c:477-479`,WaitAll|NoClear,100 ms 分片喂狗)。 /log 不是「三本」(条 5) - `App/log/sys_log_mcu.c` 的 `s_paths[]` = run / selftest / accid / alarm / start / oper / yk 七本 64B 定长文本 + `soe.log`(64B 定长二进制)+ `wdev.log` (变长取证行,`App/fs/vol_lock` 侧投,不走定长格式)。 → AGENTS 两处、总览 §2.1 与 §4 的「三本」改成「七本 + SOE(+ wdev 取证行)」。 主机单测 44 套 → 47 套(条 2,两把机器尺互证) - `scripts/check_test_coverage.py --list` 打印 47 行;`ls -d test/host/*/` = 48 个 一级目录,唯一没有 `run_unit_test.py` 的是 `common/` ⇒ 47 套。 - 差额三名 = 屏板链路套件:`sb_proto`、`sb_link`(2026-10-29 入库)、 `sb_led_map`(2026-10-30 入库)。专属钩子仍是 22 根 ⇒ 无专属钩子的从 22 套变 25 套。 - 同步面:AGENTS、`docs/门禁说明/README.md`、`host-unit-tests.md` §6 台账(名单补 三名)、`docs/代码规范/pre-commit使用说明.md`、`主机单测覆盖率口径.md`(含耗时句)、 `scripts/README.md`、总览目录树、`.pre-commit-config.yaml` 三处注释、 `test/host/sys_log_mcu/sys_log_mcu_test.c` 一处注释。 - 带日期戳的历史测量值按纪律冻结不动:28 套、41 套 ~105 s、40 套/18 套那三处。 本仓 LVGL 版本(条 6) - vendored 版本 = `Lib/lvgl/lv_version.h:9-11` 的 9.3.0;总览两处「LVGL v9.2」与 `gen_arch_svg.py` 图内文字改 v9.3 并重跑产物(脚本与产物同一笔,`generated-fresh` 逐字节核)。 - **刻意不动**:`docs/内存管理/RAM使用分析与优化方案.md` 两处「LVGL v9.2 文档」+ GitHub `release/v9.2` 分支 URL ——那是对外游某分支文档的引用,不是本仓版本声明。 触发口径与栈值(条 4、7、9、10) - ADC 映射 §1「ADC1 为软件触发」是旧口径:`Src/adc.c:53` 实为 `ADC_EXTERNALTRIGCONV_T3_TRGO` → 改成 TIM3 TRGO,并补一句「TIM3 与 TPAFE 的 CONVST 之间没有硬件耦合,仍不构成共同采样触发」,缺陷结论本身不变。 - 同文档 adcTask 栈 512 → 640 words(`Src/freertos.c:159 .stack_size = 640 * 4`)。 - `docs/UI设计/UI_设计架构.md` 的 uiTask 栈「16384 words」是单位错: 实为 4096 words = 16384 B(`Src/freertos.c:218`),顺手补上行号。 - `App/app/app_main.c:197` 注释「DE = PD0」陈旧:`:198` 实传 `ST_DIR4_*` (V0.1 实板 PA15,V0.0 才是 PD0)→ 注释改指 V0.1 网表报告的引脚迁移表。 - 两处已退役编号 `N-18`(ADC 映射、V0.1 网表报告)换成登记簿条目名 「双转换器无共同采样触发」。 核实后确认「文档没问题」的三条(条 8、12、13) - 黄金参考「菜单 8 状态 + 内容页 66 张」:`simulator/tests/ref/` 实测 66 个 `shot_*.bin`,与 `ui-golden` 钩子显示名一致 → 不改。 - `App/task/rs485_task.c` 存在但 `Src/freertos.c` 不创建它(grep 无 rs485): AGENTS 与总览都已写明「尚未接线 / 暂缓」,图侧画成「实现待接线」→ 不改。 - 104 端口 `:2404` 是工厂默认、可经 `/config.ini` `[comm]` 改 → 总览索引表补 「默认」二字;「uiTask 10 ms 节拍」核实 = `ui_tokens.h:65 UI_TASK_TICK_MS 10` ✓。 并行勘查会话的同族发现(它扫「文档声明数 vs 真值」,逐处独立复验后采纳 3 处) - `App/protocol/iec104/iec104_link.h` 头注释写「端口:TCP 2404(§1.5 B0 定义)」, 同文件下方已写「服务端口真源已迁 [comm] 段」= 自相矛盾 → 补「工厂默认」并指回 [comm] 与「下次重 bind 即生效」。 - `scripts/gen_task_table.py` 的 iec104Task 描述「:2404 监听」→「工厂默认 :2404 监听」,`--write` 重生成 README 任务表(AGENTS 表无描述列,实测未被改写)。 - `App/log/README.md` 五行写入点行号全漂:事故 181→188、告警 184→191、启动 176→183(`App/task/protect_task.c`);操作 `iec104_pts.c` 574/602→582/610、 `default_task.c` 242→266、`sys_log_mcu.c` 646→682;遥控 573/576/582/586/596/ 598/609 → 581/584/590/594/604/606/617(宿主 `sim_syslog.c:245` 原值正确、未动)。 - **撤销一条**:我早先记的「`docs/算法/继电保护功能复现规划.md:225` 声称记录本已 有消费者」与原文不符——:225 实为规划表里「动作/SOE 记录 | 复用 `sys_log`」一行, 属能力复用口径、不是当前态断言 → 不改。教训:**改引用前逐处读现文,别照自己 上一轮的笔记盲替**(这条比改中的每一处都值钱)。 - 同一批勘查还报了约 12 处「`2404` 缺『工厂默认』限定词」(AGENTS、`App/protocol/ README.md`、`Src/freertos.c` 注释、5 篇 docs、`test/host` 工具,含一张配图生成器 要连产物同批重跑)与 `测试开发标准流程.md`「像素层 60 内容页」、 `.pre-commit-config.yaml` uinav 注释「快检档 18 张黄金参考」(后者写时为真、 描述的却是钩子现有行为):**本笔刻意不做**,另开任务空间处理——那批要同时动 AGENTS.md 与 `.pre-commit-config.yaml` 两处单写者面,与并行会话撞车的代价更高。 - 端口生效语义(用户裁定 2026-10-30):产品基线写「公共地址与端口可配…改后需重启 生效」,代码 `App/task/iec104_serv.c` 是「ca 改后要重启;port 下次重 bind 才生效」 ——裁定**按代码语义要改**处理(改了端口应立刻重 bind),属固件行为变更,另开任务 且需真机验证;`.serialtest/config.json` 写死 `i104: listening on 2404` 必检锚点 是同题(现场改端口会让串口回归假失败),一并留给那条任务。 自查(任务树内) - `check_doc_links.py --sections-strict`:12 个改动 .md 逐个 rc=0; `check_mermaid.py` 改动 .md 全 rc=0;`check_generated_fresh.py` PASS (25 个生成器 / 21 一致 / 4 存量基线);`check_nested_comment.py --all` PASS (449 文件,新发 0、存量 2);`check_bom_drift.py --all` 99 个名单文件无漂移。 - 编译:`.dsh/skills/keil-build` 退出码 0(0 错误 0 警告), Program Size Code=246720 RO=29836 RW=2052 ZI=149708 —— 与本树凭证同码, 证明这批改的确实只有注释与文档。 - 字节体检:23 个文件无 CR / U+FFFD / 行尾空格 / 缺末行换行;`ui_task.c` 被编辑后 丢的 3 字节 BOM 已补回(bom-guard 复验通过);5 处「三连空行」逐文件与 HEAD 比对确认是存量(Python 生成器的 PEP8 双空行与个别 C 文件原有空行)。 - 一次性工具 `ci-artifacts/fix_docs2.py`:断言式规则表,按文件读一次、在演化文本上 逐条替换、一次写盘。第一版写成「每条规则各自从启动时快照起算」,同一文件后一条 把前一条整体覆盖,只有每文件最后一条活下来——靠「改完再全仓复扫旧形态」当场抓到, 这条判据本身已值得写进流程。 - 待办:`git add` 后需重跑 `ram_baseline.py --update`(体积凭证按 git 索引内容生成, 未 staged 的文件不在凭证里,`check_size_gate.py --local` 现在因此报红)。 - 未 commit、未 push(AGENTS.md 边界:提交属 Ask first)。
143 lines
7.0 KiB
C
143 lines
7.0 KiB
C
/*
|
||
* 模块名称: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 <stdint.h>
|
||
|
||
#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 */
|