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 后)。
This commit is contained in:
@@ -55,6 +55,7 @@ U_STARTDT_ACT, U_STARTDT_CON = 0x07, 0x0B
|
||||
U_TESTFR_ACT, U_TESTFR_CON = 0x43, 0x83
|
||||
|
||||
T_M_SP_NA_1, T_M_DP_NA_1, T_M_ME_NC_1 = 1, 3, 13
|
||||
T_M_SP_TB_1 = 30 # single point + CP56Time2a (B9 SOE spontaneous)
|
||||
T_C_SE_NC_1, T_C_DC_NA_1 = 50, 52
|
||||
T_C_IC_NA_1, T_C_RD_NA_1 = 100, 102
|
||||
|
||||
@@ -109,6 +110,28 @@ def asdu(type_id, vsq, cot, cot_hi, ioa=None, payload=b""):
|
||||
return h + payload
|
||||
|
||||
|
||||
def cp56_str(b):
|
||||
"""Decode CP56Time2a (7B) -> 'YYYY-MM-DD HH:MM:SS.mmm' (UTC) or 'IV-...'.
|
||||
B9: M_SP_TB_1 spontaneous time tag (sec-resolution, ms field = sec*1000)."""
|
||||
iv = (b[2] & 0x80) != 0
|
||||
ms_min = b[0] | (b[1] << 8)
|
||||
minute = b[2] & 0x3F
|
||||
hour = b[3] & 0x1F
|
||||
day = b[4] & 0x1F
|
||||
mon = b[5] & 0x0F
|
||||
year = 2000 + (b[6] & 0x7F)
|
||||
sec = ms_min // 1000
|
||||
ms = ms_min % 1000
|
||||
try:
|
||||
import datetime
|
||||
d = datetime.datetime(year, mon, day, hour, minute, sec, ms * 1000)
|
||||
s = d.strftime("%Y-%m-%d %H:%M:%S") + (".%03d" % ms)
|
||||
except ValueError:
|
||||
s = "%04d-%02d-%02d %02d:%02d:%02d.%03d" % (
|
||||
year, mon, day, hour, minute, sec, ms)
|
||||
return ("IV " if iv else "") + s
|
||||
|
||||
|
||||
class Master(object):
|
||||
"""Minimal 104 master. k/w/t are covered by "S after every recv batch"
|
||||
(far faster than t1=15s / t2=10s), so no timers are needed here."""
|
||||
@@ -208,26 +231,32 @@ class Master(object):
|
||||
n = vsq & 0x7F
|
||||
obj = a[6:]
|
||||
|
||||
if type_id in (T_M_SP_NA_1, T_M_DP_NA_1):
|
||||
pts = []
|
||||
if type_id in (T_M_SP_NA_1, T_M_DP_NA_1, T_M_SP_TB_1):
|
||||
# B9: M_SP_TB_1 (30) = SIQ + CP56Time2a (8B/point); NA forms = 1B.
|
||||
esz = 8 if type_id == T_M_SP_TB_1 else 1
|
||||
pts = [] # (ioa, raw, elem_off_in_obj)
|
||||
if sq:
|
||||
base = get_ioa(obj)
|
||||
for i in range(n):
|
||||
pts.append((base + i, obj[3 + i]))
|
||||
pts.append((base + i, obj[3 + i * esz], 3 + i * esz))
|
||||
else:
|
||||
off = 0
|
||||
for _ in range(n):
|
||||
pts.append((get_ioa(obj, off), obj[off + 3]))
|
||||
off += 4
|
||||
for ioa, raw in pts:
|
||||
pts.append((get_ioa(obj, off), obj[off + 3], off + 3))
|
||||
off += 3 + esz
|
||||
for ioa, raw, eoff in pts:
|
||||
val = (raw & 0x03) if type_id == T_M_DP_NA_1 else (raw & 0x01)
|
||||
iv = (raw & 0x80) != 0
|
||||
self.yx[ioa] = (val, iv)
|
||||
ts = ""
|
||||
if type_id == T_M_SP_TB_1:
|
||||
ts = " ts=%s" % cp56_str(obj[eoff + 1:eoff + 8])
|
||||
self.events.append(("yx", (ioa, val, iv, cot)))
|
||||
if ioa in YX_NAMES or cot == COT_SPONT:
|
||||
log("RX I %s ioa=%d val=%d iv=%d cot=%d [N(R)=%d]"
|
||||
% ("M_DP" if type_id == T_M_DP_NA_1 else "M_SP",
|
||||
ioa, val, 1 if iv else 0, cot, nr))
|
||||
log("RX I %s ioa=%d val=%d iv=%d cot=%d%s [N(R)=%d]"
|
||||
% ({T_M_DP_NA_1: "M_DP", T_M_SP_NA_1: "M_SP",
|
||||
T_M_SP_TB_1: "M_SP_TB"}[type_id],
|
||||
ioa, val, 1 if iv else 0, cot, ts, nr))
|
||||
elif type_id == T_M_ME_NC_1:
|
||||
pts = []
|
||||
if sq:
|
||||
|
||||
Reference in New Issue
Block a user