728d60675f
三条道在同一棵树上交付,故合并成**一次权威构建**(@build 0 错 0 警,Code=235236 RO=26308 RW=1572
ZI=136924)后同笔入库;台账已按该构建回填(`ram_baseline.py --update` + `--write`、`chec
k_mem_numbers.py
--write`),凭证与实际 .map 同源。归属如下:
· **通讯设置页**(道G task-9):新增 `Drivers/BSP/UI/ui_page_comm.{c,h}`(通道六注入,与死
区页同骨架同纪
律),`#page=comm` 绑定使 bound **21 → 22**、内容页黄金 **47 → 53**(旧 47 页字节零变化,
新增六态含
WAIT/OK 两张);`iec104_comm` 脏位 + store_save(**无事件位**)挂 defaultTask 同点位
轮询;`port_104` 由
宏改走 getter、`IEC104_SERV_PORT` 删除;字体 +听/监(185 字全覆)。**实机链已闭环**:FTP 外改
[comm]
的 ca/port → 复位 → 串口出现 `section 'comm' (2 keys)` 与 `i104: listening o
n 9004` → 还原 2404,全程无
FAIL ⇒「重启即生效 + 端口真值」不是推断是实测。上限核对 6/8 段、2/16 键,超限现象由 TC-CONFIG-056/057
钉死(注册 -1 + `comm init: FAIL` 锚 + getter 回 ROM),**不抬宏**。
· **开机账本**(道F task-12):新增 `App/util/boot_stats.{c,h}`——上电次数落 SD2506 用户
SRAM(magic +
小端计数 + 版本 + 异或校验),一次上电 = 1 读 + ≤1 写,**零 NAND、零卷锁、零配置段**;四态互斥
UNKNOWN / FRESH / CONTINUED / RESTARTED(BLF=1 才算电池域掉过电),**读失败绝不回写**;
文案口径「自 RTC
电池周期起」,**禁写终身次数**。复位原因优先级链从 `app_main.c` 的 if-else 收进一份真源(串口与屏幕同
源);104 四个只增不清计数 + `iec104_serv_get_stats()` 留在 serv、未塞进 link(会被 open
/close 洗掉)。
运行时长按用户裁定 = **只报本次开机以来**,零新增代码。新套件 `test/host/boot_stats` 21/21,五处注入
**全部有牙**(含「漂一字节被校验和挡下而不是报 256 倍错账」)。
· **adc_cal 可诊断化**(道E task-43):三个 volatile 计数 + `adc_cal_get_stats()`
/ `adc_cal_note_persisted()`
(水位**盖写非自增**,因为一次 save 覆盖整档,数 save 会留假未落盘);「差额 × 脏位」三态语义唯一落在
`adc_cal.h`,其中「差额 > 0 且脏位恒 0」= 绕过语义 setter 的静默丢失;TC-ADCCAL-019~023。
三条**必须留档的自纠**(都是执行道自己报的,我不替他们润色):
① stdint 那条断链是**宿主套件抓到、通讯道先修**,采集道承认自己是重复添加者并删掉自己那份;
② 「exit 2 不是证据,断言变红才是」——采集道第一版反证注入了未声明标识符导致编译错,差点当成功;
③ 套件汇总行原写 `s_total/s_total`,失败时会打印「21/21 PASSED (failed=1)」这种自相矛盾的行,已改。
另修:`check_registration.py` 的 `SIM_EXEMPT` 加一行豁免 `App/util/boot_stats.c
`(依赖 I2C 与 RCC 位掩码,
MCU 专属;核过 `sys_clock.c`/`sha256.c` 同形态先例),注册反向校验 exit 1 → 0。发号 TC-IEC1
04-034、
TC-UIDATA-029 已实抬游标(`TC-IEC104-033→034`、`TC-UIDATA-026→029`)。
门禁:id-check --strict 与 plain 均 rc=0、doc-links --all rc=0、注册校验 0、cppche
ck / coding-standard /
bom-drift 0;iec104 179/179、ui_data 295/295、boot_stats 21/21、adc_cal 11
7/117、run_par 67/67;菜单 8 状态
池 5368 不变、内容页 53/53。**已知遗留**:内容页数 47 → 53 的全仓文本同步尚未做(另卡跟踪),本笔只保
证代码与黄金一致;面板注键真机那一腿缺 CLI,交台架道补。
171 lines
7.2 KiB
C
171 lines
7.2 KiB
C
/*
|
||
* 模块名称:IEC 60870-5-104 通讯参数段([comm] 配置)实现
|
||
* 模块功能:/config.ini [comm] 段的 RAM 副本 / 工厂默认 / 键表与注册 +
|
||
* getter 合法带自愈 + setter 守卫。契约、线程约定与生效语义
|
||
* (改后重启生效、不挂回调不置事件位、主站地址不做)→
|
||
* iec104_comm.h 文件头;键表口径 → docs/子系统设计/网络配置说明.md
|
||
* §3.6;热生效代价分析 → docs/子系统设计/IEC104上行设计说明.md §8。
|
||
* 适用平台:STM32F407ZGTx / PC 模拟器(双端编译:模拟器运行期无 104 任务,
|
||
* 本 TU 不产生行为,按双端注册口径入 CMakeLists 保持构建事实一致)
|
||
* 创建日期:2026-10-26(道G task-8,「UI 补齐」第 5 项非 UI 前置)
|
||
* 修改记录:
|
||
* 2026-10-26 DTU 项目组 创建:实现体自 iec104_pts.c 尾段**同笔整体迁移**
|
||
* (零逻辑改动)。动因 = coding-standard
|
||
* CODE-RULE-17 (2.2)「非 static 函数须在配对头文件
|
||
* 声明」按文件名配对判定——初版把实现放 iec104_pts.c
|
||
* 而声明在 iec104_comm.h,钩子按规则判红;本模块
|
||
* 本就是独立配置段,配对拆分即正解。
|
||
*/
|
||
#include "iec104_comm.h"
|
||
|
||
#include <string.h>
|
||
|
||
#include "cfg_file.h"
|
||
|
||
/* ---------------- [comm] RAM 副本与工厂默认 ----------------
|
||
* 引擎属主 = defaultTask:本段只被 cfg_file_load/save/poll 通用遍历,本文件
|
||
* 不触引擎任何写路径;内核前注册点 = app_main.c(「先注册后 load」契约,同
|
||
* adc_cal_init / run_par_init)。App/protocol 零日志惯例保持——注册失败留痕
|
||
* 由调用方 app_main.c 锚点负责,getter/setter 纯 RAM 不打日志(iec104Task
|
||
* 深链省栈,AGENTS 日志成本口径)。 */
|
||
|
||
/* RAM 副本(引擎原地更新;u16 半字对齐读原子,撕裂域 = 单字段新旧值) */
|
||
typedef struct {
|
||
uint16_t ca; /* 公共地址(= 链路地址,ASDU 头 2B,点表 §1.5 口径) */
|
||
uint16_t port; /* 被控站 TCP 监听端口 */
|
||
} iec104_comm_t;
|
||
|
||
static iec104_comm_t s_comm;
|
||
static const iec104_comm_t s_comm_rom = {
|
||
IEC104_COMM_DEF_CA, IEC104_COMM_DEF_PORT
|
||
};
|
||
|
||
/* 落盘脏位(B7 形态,run_par/prot_set 同款):setter 成功置 1,
|
||
* iec104_comm_store_save() 写盘成功清 0。**不挂回调、不加事件位**(task-8
|
||
* D5 裁定的持久化面延续):defaultTask 每轮无条件调 store_save,无脏空转。
|
||
* 单字节标志:uiTask 写、defaultTask 读清——置 1 幂等、清零窗口最坏
|
||
* 多跑一轮 save,语义无损(run_par s_dirty 同一条竞态分析)。 */
|
||
static volatile uint8_t s_comm_dirty;
|
||
|
||
/* 键表(s_comm 布局内偏移,同 net_cfg/ftp_cfg 写法) */
|
||
static const cfg_key_def_t s_comm_keys[] = {
|
||
{ "ca", CFG_VAL_UINT, offsetof(iec104_comm_t, ca), 2u },
|
||
{ "port", CFG_VAL_UINT, offsetof(iec104_comm_t, port), 2u },
|
||
};
|
||
|
||
/* 函数定义区 */
|
||
|
||
/*
|
||
* 函数功能:置工厂默认并向引擎注册 [comm] 段(幂等,内核前)
|
||
* 返回值:0 注册成功;-1 重复注册/段数超限(RAM 副本已复位,adc_cal 同款) int
|
||
* 限定条件:先于 defaultTask 的 cfg_file_load;不挂回调(改后重启生效)
|
||
*/
|
||
int iec104_comm_init(void) {
|
||
cfg_section_reg_t reg;
|
||
|
||
(void)memcpy(&s_comm, &s_comm_rom, sizeof(s_comm));
|
||
s_comm_dirty = 0u;
|
||
|
||
(void)memset(®, 0, sizeof(reg));
|
||
reg.name = IEC104_COMM_SEC;
|
||
reg.p_data = &s_comm;
|
||
reg.p_defaults = &s_comm_rom;
|
||
reg.data_size = sizeof(s_comm);
|
||
reg.p_keys = s_comm_keys;
|
||
reg.key_count = (uint8_t)(sizeof(s_comm_keys) / sizeof(s_comm_keys[0]));
|
||
reg.p_cb = NULL; /* 不挂回调:iec104Task 启动读 getter(D5 口径) */
|
||
return cfg_file_register(®);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:取生效公共地址
|
||
* 返回值:RAM 副本 ca,带外回退工厂默认;恒 ∈ IEC104_COMM_CA_MIN..MAX uint16_t
|
||
* 限定条件:无(任意上下文,纯读)
|
||
*/
|
||
uint16_t iec104_comm_ca(void) {
|
||
uint16_t v = s_comm.ca;
|
||
|
||
if ((v < IEC104_COMM_CA_MIN) || (v > IEC104_COMM_CA_MAX)) {
|
||
return IEC104_COMM_DEF_CA;
|
||
}
|
||
return v;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:取生效监听端口
|
||
* 返回值:RAM 副本 port,带外回退工厂默认;恒 ∈ IEC104_COMM_PORT_MIN..MAX uint16_t
|
||
* 限定条件:无(任意上下文,纯读)
|
||
*/
|
||
uint16_t iec104_comm_port(void) {
|
||
uint16_t v = s_comm.port;
|
||
|
||
if ((v < IEC104_COMM_PORT_MIN) || (v > IEC104_COMM_PORT_MAX)) {
|
||
return IEC104_COMM_DEF_PORT;
|
||
}
|
||
return v;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:写 ca 进 RAM 副本(守卫 = getter 同带;非法不提交)
|
||
* 入口参数:ca - 公共地址 uint16_t
|
||
* 返回值:0 提交并置落盘脏;-1 带外(副本不动、不置脏) int
|
||
* 限定条件:UI 保存交接路(通讯设置页经 ui_data 通道六转进来)与宿主测试;
|
||
* 落盘 = defaultTask 每轮无条件 iec104_comm_store_save()(无事件位)
|
||
*/
|
||
int iec104_comm_ca_set(uint16_t ca) {
|
||
if ((ca < IEC104_COMM_CA_MIN) || (ca > IEC104_COMM_CA_MAX)) {
|
||
return -1;
|
||
}
|
||
s_comm.ca = ca;
|
||
s_comm_dirty = 1u;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:写 port 进 RAM 副本(守卫 = getter 同带;非法不提交)
|
||
* 入口参数:port - 监听端口 uint16_t
|
||
* 返回值:0 提交并置落盘脏;-1 带外(副本不动、不置脏) int
|
||
* 限定条件:同 iec104_comm_ca_set(生效时刻 = 下一次重 bind 监听)
|
||
*/
|
||
int iec104_comm_port_set(uint16_t port) {
|
||
if ((port < IEC104_COMM_PORT_MIN) || (port > IEC104_COMM_PORT_MAX)) {
|
||
return -1;
|
||
}
|
||
s_comm.port = port;
|
||
s_comm_dirty = 1u;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:落盘脏探查(非消费)——页面保存行 WAIT/OK 的唯一判定源
|
||
* 返回值:1 有未落盘改动;0 干净 uint8_t
|
||
* 限定条件:无(任意上下文纯读)
|
||
* 函数说明:脏位是全段级(ca+port 一次 save 一起落):归零只证明
|
||
* 「这一档落盘了」,不证明「我这一项单独落盘」;104 侧无自动
|
||
* 写者(ca/port 不经 C_SE 整定),比 run_par 少一条「他人再改」
|
||
* 干扰,页面仍按「结果锁存」画(run_par 页同口径,别做两套)
|
||
*/
|
||
uint8_t iec104_comm_dirty_peek(void) {
|
||
return s_comm_dirty;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:执行落盘(整档外科 save 成功后清脏;失败脏保持 = 下轮重试)
|
||
* 返回值:0 成功/无脏;-1 写盘失败 int
|
||
* 限定条件:**defaultTask(配置引擎属主)上下文**——run_par_store_save()
|
||
* 同一调用点旁挂,每轮无条件
|
||
* 函数说明:失败不打日志是刻意的(App/protocol 零日志惯例,本文件无 DBG):
|
||
* cfg_file_save 自身已有 DBG_ERROR 留痕 + 脏位保留 = 屏上 ERR 的
|
||
* 判定源,再打一条就是重复锚点(AGENTS 日志成本口径)
|
||
*/
|
||
int iec104_comm_store_save(void) {
|
||
if (s_comm_dirty == 0u) {
|
||
return 0;
|
||
}
|
||
if (cfg_file_save() != 0) {
|
||
return -1;
|
||
}
|
||
s_comm_dirty = 0u;
|
||
return 0;
|
||
}
|