1d96fc9f87
- 新增 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 后)。
161 lines
5.7 KiB
C
161 lines
5.7 KiB
C
/*
|
||
* 模块名称: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;
|
||
}
|