Files
wandering1 1d96fc9f87 feat(soe): B9 动作记录 + 时标 SOE(L4-4/L4-5)+ 104 M_SP_TB_1 自发
- 新增 App/log/soe_rec.{c,h}:64B 定长 SOE 记录纯编解码(CP56Time2a
  时标 + 类型 + 元件号 + 单调序号 + 值 + sum8 撕裂校验),零 I/O 零 OS
  依赖,主机 L0.5 覆盖(32 断言,含与 iec104 CP56 解码器交叉互证)
- 新增 App/log/soe_store.{c,h}:protectTask 边沿投递(取时标 + 组记录 +
  SPSC 待发环/消息队列,零文件系统);defaultTask 经 syslog_drain 尾部
  排空落 /log/soe.log(卷锁 try / FA_WRITE|FA_OPEN_APPEND / 校验字节数 /
  容量满 unlink 重建,全套照 run.log 实证纪律;无卷损坏归类路径)
- protect_task.c:元件动作/返回、软压板投退、采样闭锁/恢复六类边沿
  → soe_store_post;post 内禁 DBG_*(protect 静态最坏链 872 B 不变,
  改前/改后 stack_report 九任务逐字节一致),丢弃留痕挪 drain 侧
- iec104:单点 YX 自发改 M_SP_TB_1(30) 带 CP56 时标(总召/周期保持
  M_SP_NA_1,单测两种都断言);link cfg 新增 epoch_ms 时标源回调
  (NULL/无效 → IV 位如实置位);serv 绑 sys_clock(秒域时基,
  ms 域 = 秒×1000,不造假亚秒)
- 主机单测:test/host/soe/(32 断言)+ iec104 94→97(TB 时标/IV);
  iec104_master.py 解析 type 30(B6/B7 流不回归);decode_soe.py
  台架解码断言工具(seq 单调/校验和/UTC 交叉)
- 注册:uvprojx + check_registration SIM_EXEMPT 点名(MCU-only 胶水)
- 文档(我方属主区):`App/log/README.md` 增 soe 节(64B 记录/单写者/
  post 禁 DBG/UTC 交叉坑);`docs/子系统设计/IEC104上行设计说明.md`
  §1 范围行 + §3.2 时标注 + §7 台架行 + §8 第 2 条销项 + 变更记录行

台架腿(bench 锁 B9,release --bench-pass):run_boot_test PASS;
--b7-pre 21/21、--b7-post 13/13(B6/B7 回归 + 确定性动作边沿);
自发 YX 上送 M_SP_TB_1 带 ts(11:00:51 动作 / 11:00:53 复归);
FTP 下载 /log/soe.log 30 条记录 decode PASS(seq 0..28 单调、
ELM_ASSERT 14 条、UTC 交叉一致);定值/压板已恢复默认。

台账 +1088 B(SPSC 环 512 + 静态 FIL 528 + 计数器)随合并由 Lead
回填(ram_baseline --write + check_mem_numbers --write)。

gate: only mem-budget/mem-numbers pending lead ledger backfill
(pre-commit --all-files: 29 Passed / 2 Failed / 0 Skipped;
hygiene self-check OK: no BOM/CRLF/mojibake/trailws/non-ASCII literal)

sync 追平 v2-on-ui(B8 freq + N33 抬栈)后复跑:@build 0/0
(Code=209492 RW=1104 ZI=154184);stack_report 九任务全 PASS、
protectTask 872 B/57% 不变;ci_local L0.5 15 runner 951/951
(freq 46 / soe 32 / iec104 97 / prot_set 90 / prot_elm 47)。
顺带修 protect_task.c 陈旧注释:定值巡检 31 项 → 40 项(B8 后)。
2026-10-12 19:38:13 +08:00

161 lines
5.7 KiB
C
Raw Permalink 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.
/*
* 模块名称:SOE Record — 事件顺序记录编解码(实现)
* 模块功能:实现 soe_rec.h——64B 定长记录的构建(epoch→CP56Time2a,Hinnant
* civil 日历算法,与 iec104_asdu.c 同一算法族但本模块零依赖)与
* 解析(版本 + sum8 校验和硬门槛)。
* 线程约定:全部纯函数,无状态 → 任意上下文可调(生产者 = protectTask,
* 落盘/读取 = defaultTask / 主机工具)。
* 创建日期:2026-10-12(B9)
*/
#include "soe_rec.h"
#include <string.h>
/* 2000-01-01 00:00:00 UTC 的 epoch 秒(CP56 年字段下限,钳位用) */
#define EPOCH_2000_S 946684800uL
/* 2127-12-31 23:59:59 UTC(年字段 7bit 上限 2000+127,钳位用;
* = 2128-01-01(4986057600) - 1,含 2000..2124 共 32 个闰日) */
#define EPOCH_2127_END_S 4986057599uLL
/*
* 函数功能:epoch 秒 → 小端 32 位写入
*/
static void s_soe_put_u32(uint8_t *buf, uint32_t v) {
buf[0] = (uint8_t)(v & 0xFFu);
buf[1] = (uint8_t)((v >> 8) & 0xFFu);
buf[2] = (uint8_t)((v >> 16) & 0xFFu);
buf[3] = (uint8_t)((v >> 24) & 0xFFu);
}
/*
* 函数功能:小端 32 位读取
*/
static uint32_t s_soe_get_u32(const uint8_t *buf) {
return (uint32_t)buf[0] | ((uint32_t)buf[1] << 8)
| ((uint32_t)buf[2] << 16) | ((uint32_t)buf[3] << 24);
}
/*
* 函数功能:距 1970-01-01 天数 → 公历(Hinnant civil_from_days,与
* iec104_asdu.c 同一算法;纯整数,ARMCC/MSVC 双端可移植)
* 入口参数:z_in = 天数;y/m/d = 输出
*/
static void s_soe_civil_from_days(uint32_t z_in, uint32_t *y, uint32_t *m, uint32_t *d) {
uint32_t z = z_in + 719468u;
uint32_t era = z / 146097u;
uint32_t doe = z - (era * 146097u);
uint32_t yoe = (doe - (doe / 1460u) + (doe / 36524u) - (doe / 146096u)) / 365u;
uint32_t yy = yoe + (era * 400u);
uint32_t doy = doe - ((365u * yoe) + (yoe / 4u) - (yoe / 100u));
uint32_t mp = ((5u * doy) + 2u) / 153u;
uint32_t mon = mp + ((mp < 10u) ? 3u : 0u);
*d = doy - (((153u * mp) + 2u) / 5u) + 1u;
if (mp >= 10u) {
mon = mp - 9u;
}
*m = mon;
*y = yy + ((mon <= 2u) ? 1u : 0u);
}
/*
* 函数功能:epoch 秒 → CP56Time2a(7B,UTC 分解)
* 入口参数:epoch_s - Unix 秒;buf - 目标 7B;iv - 1 = 置 IV 位(时间无效)
* 返回值:无
* 函数说明:钳位 [2000-01-01, 2127-12-31](CP56 年字段 7bit 域;epoch_s 为
* uint32 → 上界钳位是防御性死分支,2106 后域扩展时生效);
* ms 域 = 秒×1000(秒域时基如实编码,亚秒恒 0,不造假)。
*/
static void s_soe_epoch_to_cp56(uint32_t epoch_s, uint8_t *buf, uint8_t iv) {
uint64_t clamped;
uint32_t days, rem, y, m, d, ms_min;
clamped = epoch_s;
if (clamped < (uint64_t)EPOCH_2000_S) {
clamped = (uint64_t)EPOCH_2000_S;
}
if (clamped > EPOCH_2127_END_S) {
clamped = EPOCH_2127_END_S;
}
days = (uint32_t)(clamped / 86400uLL);
rem = (uint32_t)(clamped % 86400uLL);
s_soe_civil_from_days(days, &y, &m, &d);
if (y > 2127u) { /* 钳位上界的日历化(防 epoch 上界跨日) */
y = 2127u;
m = 12u;
d = 31u;
rem = 86399u;
}
ms_min = (rem % 60u) * 1000u; /* 秒域时基:秒如实入 ms 域(×1000),
* 亚秒部分恒 0——不造假(B9 口径) */
buf[0] = (uint8_t)(ms_min & 0xFFu);
buf[1] = (uint8_t)(ms_min >> 8);
buf[2] = (uint8_t)(((rem / 60u) % 60u) & 0x3Fu); /* min;bit7 IV 下方置 */
buf[3] = (uint8_t)((rem / 3600u) % 24u); /* hour */
buf[4] = (uint8_t)(d & 0x1Fu); /* day */
buf[5] = (uint8_t)(m & 0x0Fu); /* month */
buf[6] = (uint8_t)((y - 2000u) & 0x7Fu); /* year */
if (iv != 0u) {
buf[2] |= 0x80u; /* IV:时间无效 */
}
}
/*
* 函数功能:sum8([0..62]) 校验和
*/
static uint8_t s_soe_sum8(const uint8_t *rec) {
uint8_t s = 0u;
uint32_t i;
for (i = 0u; i < SOE_OFF_SUM; i++) {
s = (uint8_t)(s + rec[i]);
}
return s;
}
/*
* 函数功能:构建一条 64B SOE 记录(见 soe_rec.h 声明注释)
*/
void soe_rec_build(uint8_t *rec, uint8_t type, uint8_t elm_id, uint8_t flags,
uint32_t seq, uint32_t epoch_s, uint32_t value) {
if (rec == NULL) {
return;
}
(void)memset(rec, 0, SOE_REC_SIZE);
rec[SOE_OFF_VER] = (uint8_t)SOE_REC_VER;
rec[SOE_OFF_TYPE] = type;
rec[SOE_OFF_ELM] = elm_id;
rec[SOE_OFF_FLAGS] = flags;
s_soe_put_u32(rec + SOE_OFF_SEQ, seq);
s_soe_epoch_to_cp56(epoch_s, rec + SOE_OFF_CP56,
((flags & SOE_FLG_IV) != 0u) ? 1u : 0u);
s_soe_put_u32(rec + SOE_OFF_EPOCH,
((flags & SOE_FLG_IV) != 0u) ? 0u : epoch_s);
s_soe_put_u32(rec + SOE_OFF_VALUE, value);
rec[SOE_OFF_SUM] = s_soe_sum8(rec);
}
/*
* 函数功能:解析一条 64B SOE 记录(见 soe_rec.h 声明注释)
*/
int soe_rec_parse(const uint8_t *rec, soe_rec_t *out) {
if ((rec == NULL) || (out == NULL)) {
return -1;
}
if (rec[SOE_OFF_VER] != (uint8_t)SOE_REC_VER) {
return -2;
}
if (rec[SOE_OFF_SUM] != s_soe_sum8(rec)) {
return -3;
}
out->ver = rec[SOE_OFF_VER];
out->type = rec[SOE_OFF_TYPE];
out->elm_id = rec[SOE_OFF_ELM];
out->flags = rec[SOE_OFF_FLAGS];
out->seq = s_soe_get_u32(rec + SOE_OFF_SEQ);
(void)memcpy(out->cp56, rec + SOE_OFF_CP56, sizeof(out->cp56));
out->epoch_s = s_soe_get_u32(rec + SOE_OFF_EPOCH);
out->value = s_soe_get_u32(rec + SOE_OFF_VALUE);
return 0;
}