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