0b84c43d2e
批 3(LOGO 按用户裁定出局,不实现、sb_proto 的 LOGO 组帧挂待删标注): - 新增 App/algo/sb_led_map.c/h:业务状态 → 屏板 LED 位掩码的唯一映射表 (纯逻辑,主机单测可跑)。ID1 运行心跳 500ms 半周期闪(与主板 LED 同拍)、 ID2 保护动作、ID3 104 链路 RUN、ID4 采样电平故障(不选 sticky——灯要能灭)、 ID5 保护启动 picked;bit0 与 ID 6~19 恒 0。这张表参考源码里没有,属自定, 出处 docs/产品需求/产品需求设计文档.md §5.16.3 ①。 - iec104_serv 新增 iec104_serv_link_run()(透传 link 状态机,不另存镜像量)。 - ui_task.c 帧组装块每拍现采四路状态源(均为任意上下文可读观测口)折算后 传 sb_link_frame_update——替换批 2 的恒 0 占位;掩码变了帧就重组, 复用 busy/dirty 机制,无新通路。 - 主机单测 test/host/sb_led_map 17/17(位语义/恒零位/闪烁相位/tick 回绕/ NULL 契约);注册豁免走 SIM_EXEMPT(模拟器无屏板串口)。 批 4 的 4-1 双通道并发回归(真屏板在线实测): - 新增 test/host/remolcd_pull_test.py:RemoDispBus 拉帧压测客户端 (INIT→LCDMEM 10 Hz、逐帧 XOR+长度校验、往返延迟统计、--dump PBM)。 - 实测:网络侧两轮 2200 拉帧零错零超时(min 35/avg 63/max 131 ms); 屏板侧 60 s 隔离窗口 rx +233 全收、drop 零新增、bad=0、off=0、无 OFFLINE——两侧互不拖。观察项 len=7(断帧尺寸≠6,累计 7 次)出现窗口 无法归因压测、隔离窗口零新增、有效帧零损失,已挂设计说明 §5.7 待查。 附带:sb_link.h frame_update 注释回填实测耗时(509/688 µs 取代旧估算)。 Keil 0 错误 0 警告;ci_local 全绿(SKIP=nested-comment,主树脚本另查 0 新发); Code=239,064 B / RW_IRAM1 65.5 % / RW_CCM 87.9 %;覆盖率 90.1 % / 44 套件。
457 lines
19 KiB
C
457 lines
19 KiB
C
/*
|
||
* 模块名称:IEC 60870-5-104 被控站服务(板端胶水任务)
|
||
* 模块功能:监听端口来自 [comm] 段(工厂默认 2404)的 TCP server + 单连接
|
||
* socket 收发 + 把字节流喂给 iec104_link 纯逻辑层;对时经
|
||
* sys_clock_set_unix 落墙钟;数据源走 iec104_pts 默认源
|
||
* (phasor / prot_elm / prot_set)。协议语义全部在 App/protocol/iec104,
|
||
* 本文件只做 I/O 编排(分层约束)。
|
||
* 线程约定:iec104Task 独占(NET_READY 屏障后循环,100ms 节拍 + 看门狗心跳)。
|
||
* 创建日期:2026-10-12(B4)
|
||
* 修改记录:
|
||
* 2026-10-26 DTU 项目组 道G task-8:公共地址与监听端口取值来源改
|
||
* [comm] 段 getter(iec104_comm.h,改后重启生效;
|
||
* 端口另在下次重 bind 自然生效)。只改取值来源,
|
||
* 链路编排零改动。
|
||
*/
|
||
#include "iec104_serv.h"
|
||
|
||
#define DBG_LINE_BUF_MAX 128U /* 深链模块日志缓冲收紧(time_sync 先例) */
|
||
#define DBG_TAG "[I104]"
|
||
|
||
#include "iec104_comm.h" /* [comm] 段 getter:ca / 监听端口(道G task-8) */
|
||
#include "iec104_link.h"
|
||
#include "iec104_pts.h"
|
||
#include "net_socket.h"
|
||
#include "prot_set.h" /* 定值脏标志 peek(B7 落盘通知) */
|
||
#include "run_par.h" /* 运行参数脏 peek(运行参数段落盘通知,同款语义) */
|
||
#include "sys_clock.h"
|
||
#include "sys_log.h" /* task-19:对时成功落一条操作记录(SYSLOG_OPER) */
|
||
#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 先例(短写=对端收坏帧) */
|
||
#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 调用返回后拆链 */
|
||
|
||
/* ---------------- 只读观测计数(道G task-12;上电以来,跨重连**不清零**)-----------
|
||
* 与上面三枚的分工要说清:s_liveness_ctr / s_send_fail_run / s_conn_dead 是**控制量**
|
||
* ——它们为"要不要拆链"服务,会被复位、也是 8 位,拿它们当统计数字必错(拆一次链
|
||
* 就把连败清零)。下面四枚只做一件事:被 iec104_serv_get_stats() 读出去显示。
|
||
* 为什么留在本文件而不是并进 App/util/boot_stats:104 的账本归 104(分层),
|
||
* boot_stats 只管开机账;两边各一张表比一张混表好查。
|
||
* 为什么**不放进 iec104_link_t**:实例是 serv.c:48 的文件级 static,feed 拿不到指针;
|
||
* 更要紧的是 link_open/link_close 的语义会把"上电以来"洗成"本次连接"。
|
||
* 写者 = iec104Task(本文件),读者 = uiTask/feed;u32 单字读写在 Cortex-M4 上原子,
|
||
* 跨字段不保证同一瞬间——四个数之间没有"合起来才成立"的判据,混窗对显示无害。
|
||
* 到顶**饱和**不回绕:停在 42 亿一眼能看出到头,回绕成个位数会被读成"刚上电"。 */
|
||
static uint32_t s_st_conn_up; /* 主站接入次数(accept 成功且已 open link) */
|
||
static uint32_t s_st_apdu_tx; /* 成功发出的 APDU 帧数(短写不计,那算 tx_fail) */
|
||
static uint32_t s_st_tx_fail; /* 发送短写/失败次数(与连败复位无关,只累计) */
|
||
static uint32_t s_st_rx_bytes; /* 收到的字节数(帧数在 link 层重组,serv 只看得见字节) */
|
||
|
||
/*
|
||
* 函数功能:单枚观测计数 +1,到顶饱和(四枚同式,收成一处,别在四个调用点各写一遍)
|
||
* 入口参数:p - 计数变量地址 uint32_t *
|
||
* 返 回 值:无 void
|
||
* 限定条件:iec104Task 上下文(唯一写者);NULL 时无害返回
|
||
* 函数说明:**只加不清**——观测计数一旦有清零路径,"上电以来"就退化成"上次谁清零
|
||
* 以来"。p 为 NULL 直接返回(与三个对外 getter 的 NULL 契约一致)。
|
||
*/
|
||
static void s_cnt_add(uint32_t *p)
|
||
{
|
||
if (p == 0) {
|
||
return;
|
||
}
|
||
if (*p < 0xFFFFFFF0u) {
|
||
*p = *p + 1u;
|
||
}
|
||
}
|
||
|
||
/* 前向声明(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);
|
||
|
||
/* 链路 cfg:ca 占位工厂默认,任务启动经 s_apply_comm_ca() 从 [comm] 段取真值
|
||
* (道G task-8;点表口径默认 1);非 const 即此一处写、其余只读 */
|
||
static iec104_link_cfg_t s_cfg = { IEC104_COMM_DEF_CA,
|
||
s_send_apdu, s_clock_sync, s_epoch_ms };
|
||
|
||
/*
|
||
* 函数功能:link 层发送回调——经 net_send 串行 RPC 发出一个 APDU
|
||
* 入口参数:ctx 未用;apdu/len = 完整 APCI 帧
|
||
* 返回值:无
|
||
* 限定条件:s_client >= 0(在 link 调用栈内执行,即本任务上下文)
|
||
* 函数说明:短写/失败累计(ui_net_sink 先例);连续 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;
|
||
s_cnt_add(&s_st_apdu_tx); /* 只数真发出去的(道G task-12 观测计数) */
|
||
return;
|
||
}
|
||
s_cnt_add(&s_st_tx_fail); /* 短写/失败也进观测账,与下面的连败判定各管各的 */
|
||
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 写回),
|
||
* 成功时顺带落一条操作记录(道F task-19:「时钟设置」是四处操作记录
|
||
* 写入点之一,另外三处见 App/log/README.md 的五类记录对照表)
|
||
* 入口参数:ctx 未用;epoch_ms = C_CS 解析出的 UTC 时刻
|
||
* 返回值:0 成功 / -1 失败(link 层回 actcon+PN)
|
||
* 函数说明:uint32 秒域上限 2106-02-07,越界显式拒绝(CP56 域到 2127)。
|
||
* **只在真正改成功之后才投记录**:越界拒绝与 sys_clock_set_unix 失败两条腿
|
||
* 一条都不投——操作记录是审计面,写进去一条没发生过的"对时"比少一条更坏
|
||
* (TC-I104SRV-050b / 051 分别钉这两条腿)。
|
||
* 投递用 syslog_post_quiet():iec104Task 是深链任务(本文件顶部已把
|
||
* DBG_LINE_BUF_MAX 收到 128 就是同一件事的证据),不许在它的调用链上
|
||
* 内联日志行缓冲;两个投递口的收/丢判据一致,差别只在留痕位置
|
||
* (口径与理由见 App/log/README.md「两个投递口」)。
|
||
* 文案是 ROM 常量、17 字节 < SYSLOG_DESC_MAX(28):iec104Task 里不 snprintf。
|
||
*/
|
||
static int s_clock_sync(void *ctx, uint64_t epoch_ms) {
|
||
uint64_t secs = epoch_ms / 1000ULL;
|
||
int rc;
|
||
|
||
(void)ctx;
|
||
if (secs > 0xFFFFFFFFULL) {
|
||
return -1;
|
||
}
|
||
rc = sys_clock_set_unix((uint32_t)secs);
|
||
if (rc == 0) {
|
||
syslog_post_quiet(SYSLOG_OPER, "clock set by 104");
|
||
}
|
||
return rc;
|
||
}
|
||
|
||
/*
|
||
* 函数功能: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;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:把 [comm] 段公共地址取进链路 cfg(道G task-8 取值来源改造)
|
||
* 返回值:无
|
||
* 限定条件:iec104_link_init 之前调用(任务启动一次)——ca 生效语义 =
|
||
* 改后重启;端口不同:s_bind_listen 每次现取 iec104_comm_port(),
|
||
* 下次重 bind(拆链重建/网络代次重建)即生效,不必整机重启
|
||
*/
|
||
static void s_apply_comm_ca(void) {
|
||
s_cfg.ca = iec104_comm_ca();
|
||
}
|
||
|
||
/*
|
||
* 函数功能:创建并监听 [comm] port(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_comm_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");
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读 104 观测计数(契约见 iec104_serv.h 的 iec104_serv_get_stats)
|
||
* 入口参数:p_out - 输出缓冲;NULL 时无害返回 iec104_serv_stats_t *
|
||
* 返 回 值:无 void
|
||
* 限定条件:任意上下文;不取锁、不读时刻、不碰 socket、不阻塞
|
||
* 函数说明:先读进局部再整体赋出——四枚都读完后才发布,读者拿到的是"同一轮读取"的
|
||
* 四元组(中途若有新计数产生,那一枚可能偏新一格,但不会读到半改写的字)。
|
||
* 本函数**不清零、不复位任何计数**:统计口一旦能清零,"上电以来"就退化成
|
||
* "上次谁读过以来",那条账再没人敢信。
|
||
*/
|
||
void iec104_serv_get_stats(iec104_serv_stats_t *p_out)
|
||
{
|
||
iec104_serv_stats_t s;
|
||
|
||
if (p_out == NULL) {
|
||
return;
|
||
}
|
||
s.conn_up = s_st_conn_up;
|
||
s.apdu_tx = s_st_apdu_tx;
|
||
s.tx_fail = s_st_tx_fail;
|
||
s.rx_bytes = s_st_rx_bytes;
|
||
*p_out = s;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读 104 链路是否处于 RUN 态(契约见 iec104_serv.h 的 iec104_serv_link_run)
|
||
* 入口参数:无
|
||
* 返 回 值:1 = RUN;0 = 其它 uint8_t
|
||
* 限定条件:任意上下文;单枚举字读,原子
|
||
* 函数说明:直接透传 iec104_link_state(),不另存镜像量——镜像量一旦和状态机
|
||
* 分家(比如 open/close 路径漏更),屏灯就成第二本假账。link_state
|
||
* 本身只读实例里的 state 字段(App/protocol/iec104/iec104_link.c),
|
||
* 无副作用,跨任务读安全。
|
||
*/
|
||
uint8_t iec104_serv_link_run(void)
|
||
{
|
||
return (iec104_link_state(&s_link) == IEC104_ST_RUN) ? 1U : 0U;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:单次服务循环(代次自愈 → accept → 门控收 → 喂 link → 节拍)
|
||
* 返回值:无
|
||
* 限定条件:NET_READY 已置位;s_cfg/s_link 已 init
|
||
* 函数说明:门控(net_listener_armed/net_can_read 纯 RAM 读)+ 兜底节拍照
|
||
* ui_net_sink 先例;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;
|
||
|
||
/* accept 门控迁移(docs/网络/网络自愈架构设计.md §4.2):门控从 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);
|
||
s_cnt_add(&s_st_conn_up); /* 主站接入次数(上电以来;道G task-12) */
|
||
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) {
|
||
/* 收字节观测计数(道G task-12):这里数的是 recv 段返回的**字节数**,不是
|
||
* APDU 帧数——帧边界在 link 层重组,serv 这一层看不见;页面文案别写"接收帧数"。
|
||
* 加法也做饱和:n 最大 128,先留出余量再比,避免"加完才判"溢出成小数字。 */
|
||
if (s_st_rx_bytes < (0xFFFFFFF0u - (uint32_t)n)) {
|
||
s_st_rx_bytes = s_st_rx_bytes + (uint32_t)n;
|
||
} else {
|
||
s_st_rx_bytes = 0xFFFFFFF0u;
|
||
}
|
||
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();
|
||
s_apply_comm_ca();
|
||
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_comm_port());
|
||
} else {
|
||
DBG_INFO("i104: listening on %u", (unsigned)iec104_comm_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
|
||
* (运行参数段;peek 非消费 = 写盘失败天然重试,与上块 B7 语义完全同款) */
|
||
if ((run_par_dirty_peek() != 0u) && (sys_evtHandle != NULL)) {
|
||
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_RUN_PAR_SAVE);
|
||
}
|
||
osDelay(IEC104_POLL_MS);
|
||
}
|
||
}
|