Files
STM32F4-Base/App/task/iec104_serv.c
T
wandering1 8c72698666 feat(运行参数): 卡10 run_par真源收编+104段0x8020~0x8058读写映射(道D M6-①)
新增 App/algo/run_par.c/h(「数据可信」卡 10,lead 四裁落地):
- [run_par] cfg 段:遥测死区 9 键 int32 毫单位(db_ua…db_freq),工厂默认 =
  卡 6 额定 0.5% 档(500mV×4 / 5mA×4 / 500mHz);注册走 adc_cal 同款内核前
  契约(app_main_init),不挂回调 = 引擎副本即生效值;键数 9≤16 不拆段
- 真源收编(裁③):pts 层 s_yc_db 运行时副本删除,COV 每拍实时读 run_par
  (协议→算法向下依赖,同 meas 先例;无投递无缓存 = 顺序/幂等平凡成立);
  iec104_pts_yc_set_deadband()/deadband() 保留为转发壳,壳内保留"成功清
  半截确认计数"语义并由 TC-IEC104-027 追加断言钉住(裁⑤)
- 104 地址映射(裁①):0x8030~0x8038 死区 9 点 / 0x8040~0x8047 k 覆盖 /
  0x8048~0x804F b 覆盖(逐项地址本仓自定——复现规划 §5.4 权威表不在仓,
  送检点表到位后替换 run_par.c 顶部常量段一处;已在 IEC104 §3.4 表头挂账);
  路由顺序 = run_par 表先、未命中落 prot_set——0x8027~0x802A 属 prot_set
  告警段永不覆盖,TC-IEC104-029 双向往返钉死;空隙地址自然 COT14/PN
- 通道系数覆盖 = adc_cal_set 代理(裁②a):合法带/越带 [ERR] 留痕全复用,
  run_par 不存第二份系数(防双真源);单侧写 RMW 用另一侧当前生效值;
  回显 = 生效值视图(文件坏值被哨兵回退 ROM 时主站看到的就是下游吃到的);
  adc_cal 配套:+adc_cal_b_codes() 读取口(回显/RMW 用)+ set() 授权写者
  注记扩至 iec104Task 直通路径(单字原子契约同「数据可信」§4.4)
- 持久化(裁②b):新事件位 SYS_EVT_RUN_PAR_SAVE(1<<8);iec104Task 主循环
  peek 投递 → defaultTask run_par_store_save() 一条事件一趟 cfg_file_save
  整档外科(覆盖 [run_par] 9 键 + 联动 [adc_cal] 两段);失败脏保留 = 天然
  重试(B7 prot_set 同款)。C_SE 现行无"暂停/测试态"门控——文档现行如此
  不造新语义,送检若要求接线点已写进 §3.4 注(裁④)

测试:新建 test/host/run_par(TC-RUNPAR-001~008,67 断言,真实 cfg 引擎 +
真实 adc_cal + 内存虚拟盘同 adc_cal 套件先例;[run_par] 注册契约/落盘往返/
外改文件即副本生效/两段式不提交不置脏/合法带拒+RMW+生效值回显/init 幂等
重注册留痕);iec104 套扩真链 TU(cfg_file+adc_cal+run_par 非桩,141 断言:
+TC-IEC104-029 路由 + 壳清确认计数 3 断言);meas 套静态更名 s_snap*→
s_ms_snap*(与 cfg_file.c s_snap 同 TU 相撞,D1 同款改名法,226 断言不变);
iec104_serv 套补 run_par_dirty_peek 假件。id_registry 建族 TC-HOST-RUNPAR
(游标 008)+ TC-HOST-IEC104 游标 028→029;新钩子 run-par-unit-test +
iec104 钩子触发面随真链扩;uvprojx +run_par.c(合法入库)+ SIM_EXEMPT 登记
(模拟器不编 104 协议,已核实 CMake 面)。

文档:IEC104 §3.4 运行参数段表(含送检替换账/门控未来条件/代理声明)、§7、
§8 第 4 条、变更记录;网络配置说明新增 §3.5 [run_par] 键表;复现规划 §5.4
补运行参数段落实进度。**遗留(交 lead)**:数据可信 §4.4 "引擎 RAM 副本"
一节当时的措辞对象含 pts 侧副本,s_yc_db 删除后该句过时(裁③b 点名,§4.4
在写域外未动)。

gate:@build 0/0(Code=219620 RO=22036 RW=1280 ZI=132864);vs 0cbae7d:
Flash +1912B、RAM(RW+ZI) +8B(37B 新静态被链接器并洞吸收);iec104Task 栈
1456B/margin 43%、defaultTask 3032B 不变;id-check/doc-links/cppcheck/
coding-standard 实跑过;--all-files 全量 45 钩子实跑(43 绿 + 仅 mem-budget /
mem-numbers 两红 = 台账受管区 vs .map 漂移,回填属 lead 单写者区,批 1/道D-1
/D2 同口径):gate: only mem-budget/mem-numbers pending lead ledger backfill
BOM/CR/U+FFFD/字面量 ASCII 逐文件自查通过。
2026-10-25 13:15:41 +08:00

336 lines
12 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 被控站服务(板端胶水任务)
* 模块功能:2404 监听 + 单连接 socket 收发 + 把字节流喂给 iec104_link 纯逻辑
* 层;对时经 sys_clock_set_unix 落墙钟;数据源走 iec104_pts 默认源
* (phasor / prot_elm / prot_set)。协议语义全部在 App/protocol/iec104,
* 本文件只做 I/O 编排(A-4 约束 1)。
* 线程约定:iec104Task 独占(NET_READY 屏障后循环,100ms 节拍 + 看门狗心跳)。
* 创建日期:2026-10-12(B4)
*/
#include "iec104_serv.h"
#define DBG_LINE_BUF_MAX 128U /* 深链模块日志缓冲收紧(time_sync 先例) */
#define DBG_TAG "[I104]"
#include "iec104_link.h"
#include "iec104_pts.h"
#include "net_socket.h"
#include "prot_set.h" /* 定值脏标志 peek(B7 落盘通知) */
#include "run_par.h" /* 运行参数脏 peek(卡 10 落盘通知,同款语义) */
#include "sys_clock.h"
#include "sys_events.h"
#include "task_watchdog.h"
#include "dbg_log.h"
#include "cmsis_os2.h"
#include <string.h>
/* 服务周期:COV 延迟 ≤100ms(远小于 SOE 5000ms 验收线);心跳间隔同值 */
#define IEC104_POLL_MS 100u
/* 接收搬运缓冲(静态,不入栈;link 层内部另有 512B 重组缓冲) */
#define IEC104_RX_SZ 128u
/* 兜底节拍:门控判"无数据"时每 32 轮无条件收一次(ui_net_sink RM 先例,
* 防 RECV_OK 事件未收到把数据永久藏住);须为 2 的幂 */
#define IEC104_LIVENESS_DIV 32u
/* 连续发送失败阈值:同 ui_net_sink E-04(短写=对端收坏帧) */
#define IEC104_SEND_FAIL_MAX 3u
/* ---------------- 服务状态(iec104Task 独占) ---------------- */
static iec104_link_t s_link;
static int s_listen = -1;
static int s_client = -1; /* 单连接模型:MCU 上即 s_listen */
static uint32_t s_gen; /* 网络代次(重建自愈,ui_net_sink 先例) */
static uint8_t s_rx[IEC104_RX_SZ];
static uint8_t s_liveness_ctr;
static uint8_t s_send_fail_run;
static uint8_t s_conn_dead; /* 发送连败 → 本轮 link 调用返回后拆链 */
/* 前向声明(cfg 初始化引用) */
static void s_send_apdu(void *ctx, const uint8_t *apdu, uint16_t len);
static int s_clock_sync(void *ctx, uint64_t epoch_ms);
static uint64_t s_epoch_ms(void *ctx);
/* V0.1 公共地址固定 1(点表口径;可配置项挂二期) */
static const iec104_link_cfg_t s_cfg = { 1u, s_send_apdu, s_clock_sync, s_epoch_ms };
/*
* 函数功能:link 层发送回调——经 net_send 串行 RPC 发出一个 APDU
* 入口参数:ctx 未用;apdu/len = 完整 APCI 帧
* 返回值:无
* 限定条件:s_client >= 0(在 link 调用栈内执行,即本任务上下文)
* 函数说明:短写/失败累计(E-04 先例);连续 3 次 → 置 s_conn_dead,
* 由 s_service_link 在 link 调用返回后统一拆链(严禁在回调里拆)
*/
static void s_send_apdu(void *ctx, const uint8_t *apdu, uint16_t len) {
int ret;
(void)ctx;
if (s_client < 0) {
return;
}
ret = net_send(s_client, apdu, (int)len, 0);
if (ret == (int)len) {
s_send_fail_run = 0u;
return;
}
if (s_send_fail_run < 255u) {
s_send_fail_run++;
}
DBG_ERROR("i104 send short: ret=%d/%u run=%u", ret, (unsigned)len,
(unsigned)s_send_fail_run);
if (s_send_fail_run >= IEC104_SEND_FAIL_MAX) {
s_conn_dead = 1u;
}
}
/*
* 函数功能:link 层对时回调——epoch 毫秒落系统墙钟(含 SD2506 RTC 写回)
* 入口参数:ctx 未用;epoch_ms = C_CS 解析出的 UTC 时刻
* 返回值:0 成功 / -1 失败(link 层回 actcon+PN)
* 函数说明:uint32 秒域上限 2106-02-07,越界显式拒绝(CP56 域到 2127)
*/
static int s_clock_sync(void *ctx, uint64_t epoch_ms) {
uint64_t secs = epoch_ms / 1000ULL;
(void)ctx;
if (secs > 0xFFFFFFFFULL) {
return -1;
}
return sys_clock_set_unix((uint32_t)secs);
}
/*
* 函数功能:link 层时标源——当前墙钟 epoch 毫秒(B9:M_SP_TB_1 自发用)
* 入口参数:ctx 未用
* 返回值:epoch 毫秒;墙钟未建(UINT32_MAX)→ UINT64_MAX(link 置 CP56 IV)
* 函数说明:sys_clock 秒域时基 → ms 字段恒 0(不造假亚秒,B9 口径);
* 纯 RAM 推算,无 I/O、无副作用。
*/
static uint64_t s_epoch_ms(void *ctx) {
uint32_t s = sys_clock_get();
(void)ctx;
if (s == UINT32_MAX) {
return UINT64_MAX;
}
return (uint64_t)s * 1000ULL;
}
/*
* 函数功能:创建并监听 2404(socket+bind+listen,抄 ui_net_sink bind_listen)
* 返回值:0 成功(s_listen 写回)/ -1 失败
* 限定条件:net 子系统可用(NET_READY 屏障后)
*/
static int s_bind_listen(void) {
int ls;
struct net_sockaddr_in addr;
ls = net_socket(NET_AF_INET, NET_SOCK_STREAM);
if (ls < 0) {
return -1;
}
(void)memset(&addr, 0, sizeof(addr));
addr.sin_family = NET_AF_INET;
addr.sin_port = net_htons(IEC104_SERV_PORT);
addr.sin_addr.s_addr = NET_INADDR_ANY;
if (net_bind(ls, (struct net_sockaddr *)&addr, (int)sizeof(addr)) < 0) {
net_close(ls);
return -1;
}
if (net_listen(ls) < 0) {
net_close(ls);
return -1;
}
s_listen = ls;
return 0;
}
/*
* 函数功能:主动断开当前 104 连接并重建监听(协议故障/t1 超时/发送连败)
* 入口参数:why = 日志原因串(ASCII)
* 返回值:无
* 函数说明:与 EOF 路径不同——协议违例时主站不会自行断开,必须本端 close
* 促其重连;单连接模型下 client 即 listen,close 后统一重建
* (time_sync 会话收尾先例,非芯片侧 auto_relisten 风暴路径)
*/
static void s_drop_conn(const char *why) {
DBG_ERROR("i104 drop: %s", why);
if (s_client >= 0) {
net_close(s_client);
}
s_client = -1;
s_listen = -1;
iec104_link_close(&s_link);
s_send_fail_run = 0u;
s_conn_dead = 0u;
s_liveness_ctr = 0u;
if (s_bind_listen() < 0) {
s_listen = -1; /* 主循环下轮重试 */
}
}
/*
* 函数功能:link 层节拍服务 + 故障检测(poll 后查 IDLE 跳变/发送连败)
* 入口参数:now = 注入时基(tick ms)
* 返回值:无
*/
static void s_service_link(uint32_t now) {
iec104_link_poll(&s_link, now);
if (s_conn_dead != 0u) {
s_drop_conn("send fail");
return;
}
if (iec104_link_state(&s_link) == IEC104_ST_IDLE) {
s_drop_conn("protocol fault");
}
}
/*
* 函数功能:单次服务循环(代次自愈 → accept → 门控收 → 喂 link → 节拍)
* 返回值:无
* 限定条件:NET_READY 已置位;s_cfg/s_link 已 init
* 函数说明:门控(net_listener_armed/net_can_read 纯 RAM 读)+ 兜底节拍照
* ui_net_sink I-1/I-2;EOF 时 client==listen 严禁 close(net 层
* 已 auto_relisten,close 会连监听一起毁)
*/
static void s_serv_poll(void) {
uint32_t now = osKernelGetTickCount();
int n;
/* 网络重建(代次变化):旧监听/客户端随芯片复位失效 → 重建 */
if (net_get_generation() != s_gen) {
if ((s_client >= 0) && (s_client != s_listen)) {
net_close(s_client);
}
if (s_listen >= 0) {
net_close(s_listen);
s_listen = -1;
}
s_client = -1;
iec104_link_close(&s_link);
s_send_fail_run = 0u;
s_conn_dead = 0u;
s_liveness_ctr = 0u;
s_gen = net_get_generation();
(void)s_bind_listen();
return;
}
if (s_listen < 0) {
(void)s_bind_listen(); /* 启动失败/被毁:重试 */
return;
}
if (s_client < 0) {
int c;
/* F-NET-4:门控从 net_can_accept(只认 ESTABLISHED,已退役)
* 迁 net_listener_armed——旧门控把 LISTENING 窗口全挡掉,:2404 的丢
* CONNECT 探针(net_accept_locked 内)因此从不运行(与已修的 :7003
* 同型);armed=ESTABLISHED 待 accept / LISTENING /
* CLOSED+auto_relisten+残留(ESTABLISHED 放行由台架实证补齐:
* 漏它则本任务 200ms 周期抢不过对账② ~400ms 去抖、整场饿死) */
if (net_listener_armed(s_listen) == 0) {
if (net_get_sock(s_listen) == NULL) {
s_listen = -1; /* 控制块消失:交回上方重建 */
}
return;
}
c = net_accept(s_listen, NULL, NULL);
if (c >= 0) {
s_client = c;
s_send_fail_run = 0u;
s_conn_dead = 0u;
s_liveness_ctr = 0u;
iec104_link_open(&s_link, now);
DBG_INFO("i104: accepted fd=%d", c);
}
return;
}
/* 门控 + 兜底:无数据多数轮直接走 link 节拍;每 32 轮无条件收一次 */
if (net_can_read(s_client) == 0) {
if (((++s_liveness_ctr) & (IEC104_LIVENESS_DIV - 1u)) != 0u) {
s_service_link(now);
return;
}
} else {
s_liveness_ctr = 0u;
}
/* DONTWAIT 已转正为具名 API net_recv_nb——本任务在 IWDG
* required_mask 内且有 100ms 周期职责(喂狗/链路节拍/上送),禁止无限
* 阻塞 recv(文档契约见 net_socket.h net_recv 注释),非阻塞轮询是义务 */
n = net_recv_nb(s_client, s_rx, (int)sizeof(s_rx));
if (n > 0) {
iec104_link_on_rx(&s_link, s_rx, (uint16_t)n, now);
} else if (n == 0) {
/* 对端关闭:net 层已 auto_relisten;client==listen 时严禁 close */
if (s_client != s_listen) {
net_close(s_client); /* 模拟器多连接后端:数据通道独立 */
}
s_client = -1;
s_liveness_ctr = 0u;
iec104_link_close(&s_link);
if ((net_get_sock(s_listen) == NULL) && (s_bind_listen() < 0)) {
s_listen = -1;
}
return;
} else if (net_get_errno() != NET_ERR_WOULDBLOCK) {
s_drop_conn("recv err");
return;
}
s_service_link(now);
}
/*
* 函数功能:iec104Task 任务体(freertos.c USER CODE 区创建)
* 入口参数:argument 未用
* 返回值:无(不返回)
* 函数说明:NET_READY 屏障(100ms 分片 + 每片喂心跳,timeSyncTask 先例)→
* 点表/链路 init + 建监听 → 100ms 服务循环(每轮喂看门狗)
*/
void iec104_serv_task(void *argument) {
(void)argument;
if (sys_evtHandle != NULL) {
for (;;) {
uint32_t r = osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY,
osFlagsWaitAny | osFlagsNoClear, 100u);
if ((r & osFlagsError) == 0u) {
break; /* 就绪位是状态位(NoClear),不消费 */
}
task_wd_beat(TASK_WD_IEC104);
}
}
iec104_pts_init();
iec104_link_init(&s_link, &s_cfg, NULL);
s_gen = net_get_generation();
if (s_bind_listen() < 0) {
DBG_ERROR("i104: bind port %u FAIL, retry in loop",
(unsigned)IEC104_SERV_PORT);
} else {
DBG_INFO("i104: listening on %u", (unsigned)IEC104_SERV_PORT);
}
for (;;) {
task_wd_beat(TASK_WD_IEC104);
s_serv_poll();
/* 定值改动(C_SE 执行段 / YK12)→ 通知属主任务落盘 /prot_set.bin(B7)。
* peek 非消费:写盘失败时脏标志仍在,下一轮再投递 = 天然重试;
* 本任务不碰文件系统(配置引擎属主 = defaultTask,模块约定) */
if ((prot_set_dirty_peek() != 0u) && (sys_evtHandle != NULL)) {
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_PROT_SET_SAVE);
}
/* 运行参数整定(C_SE 0x8020~0x8058 段)→ 通知属主落盘 config.ini
* (卡 10;peek 非消费 = 写盘失败天然重试,与上块 B7 语义完全同款) */
if ((run_par_dirty_peek() != 0u) && (sys_evtHandle != NULL)) {
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_RUN_PAR_SAVE);
}
osDelay(IEC104_POLL_MS);
}
}