e4d70f9fcb
证据源 = 主树 ci-artifacts/archify/architecture-dtu-overview-20261030-095132/ evidence.md §11。该目录不入库,故把每条的判据与实测源抄进正文,别让证据只活在 一台机器上。处置口径:能改的改成与代码同源,核实后确认文档没问题的明确写「不改」。 判停点与就绪屏障(条 1、条 3) - AGENTS 原写 `protect_task.c:98` 直接停走判据:`:98` 是 `s_prev_cal_lock` 变量, 判停点是 `App/task/protect_task.c:268 if (adc_cal_prot_blocked() != 0u)`, 判定本体在标定层 `adc_cal_prot_blocked()`、protectTask 只是消费者 → 改引用, 同步 `ui_task.c:240` 页面注释与 `docs/任务规划/数据可信.md` 的同一处行号。 - `SYS_EVT_FTP_READY` 有生产者(`Src/freertos.c:490`,注释自陈预留)无消费者; `SYS_EVT_TIMESYNC_READY` 全仓无生产者也无消费者(只有定义与测试桩)。 AGENTS、`sys_events.h` 位注释、屏障配图(生成器 + 产物)三处从「四个就绪位 并列」改成「bit0/bit1 在用 + bit2/bit3 预留」,并指向 `docs/架构设计/项目启动顺序管理.md` §3.2 的逐位对照表。ftpTask 实际等的是 `NET|STORAGE`(`Src/freertos.c:477-479`,WaitAll|NoClear,100 ms 分片喂狗)。 /log 不是「三本」(条 5) - `App/log/sys_log_mcu.c` 的 `s_paths[]` = run / selftest / accid / alarm / start / oper / yk 七本 64B 定长文本 + `soe.log`(64B 定长二进制)+ `wdev.log` (变长取证行,`App/fs/vol_lock` 侧投,不走定长格式)。 → AGENTS 两处、总览 §2.1 与 §4 的「三本」改成「七本 + SOE(+ wdev 取证行)」。 主机单测 44 套 → 47 套(条 2,两把机器尺互证) - `scripts/check_test_coverage.py --list` 打印 47 行;`ls -d test/host/*/` = 48 个 一级目录,唯一没有 `run_unit_test.py` 的是 `common/` ⇒ 47 套。 - 差额三名 = 屏板链路套件:`sb_proto`、`sb_link`(2026-10-29 入库)、 `sb_led_map`(2026-10-30 入库)。专属钩子仍是 22 根 ⇒ 无专属钩子的从 22 套变 25 套。 - 同步面:AGENTS、`docs/门禁说明/README.md`、`host-unit-tests.md` §6 台账(名单补 三名)、`docs/代码规范/pre-commit使用说明.md`、`主机单测覆盖率口径.md`(含耗时句)、 `scripts/README.md`、总览目录树、`.pre-commit-config.yaml` 三处注释、 `test/host/sys_log_mcu/sys_log_mcu_test.c` 一处注释。 - 带日期戳的历史测量值按纪律冻结不动:28 套、41 套 ~105 s、40 套/18 套那三处。 本仓 LVGL 版本(条 6) - vendored 版本 = `Lib/lvgl/lv_version.h:9-11` 的 9.3.0;总览两处「LVGL v9.2」与 `gen_arch_svg.py` 图内文字改 v9.3 并重跑产物(脚本与产物同一笔,`generated-fresh` 逐字节核)。 - **刻意不动**:`docs/内存管理/RAM使用分析与优化方案.md` 两处「LVGL v9.2 文档」+ GitHub `release/v9.2` 分支 URL ——那是对外游某分支文档的引用,不是本仓版本声明。 触发口径与栈值(条 4、7、9、10) - ADC 映射 §1「ADC1 为软件触发」是旧口径:`Src/adc.c:53` 实为 `ADC_EXTERNALTRIGCONV_T3_TRGO` → 改成 TIM3 TRGO,并补一句「TIM3 与 TPAFE 的 CONVST 之间没有硬件耦合,仍不构成共同采样触发」,缺陷结论本身不变。 - 同文档 adcTask 栈 512 → 640 words(`Src/freertos.c:159 .stack_size = 640 * 4`)。 - `docs/UI设计/UI_设计架构.md` 的 uiTask 栈「16384 words」是单位错: 实为 4096 words = 16384 B(`Src/freertos.c:218`),顺手补上行号。 - `App/app/app_main.c:197` 注释「DE = PD0」陈旧:`:198` 实传 `ST_DIR4_*` (V0.1 实板 PA15,V0.0 才是 PD0)→ 注释改指 V0.1 网表报告的引脚迁移表。 - 两处已退役编号 `N-18`(ADC 映射、V0.1 网表报告)换成登记簿条目名 「双转换器无共同采样触发」。 核实后确认「文档没问题」的三条(条 8、12、13) - 黄金参考「菜单 8 状态 + 内容页 66 张」:`simulator/tests/ref/` 实测 66 个 `shot_*.bin`,与 `ui-golden` 钩子显示名一致 → 不改。 - `App/task/rs485_task.c` 存在但 `Src/freertos.c` 不创建它(grep 无 rs485): AGENTS 与总览都已写明「尚未接线 / 暂缓」,图侧画成「实现待接线」→ 不改。 - 104 端口 `:2404` 是工厂默认、可经 `/config.ini` `[comm]` 改 → 总览索引表补 「默认」二字;「uiTask 10 ms 节拍」核实 = `ui_tokens.h:65 UI_TASK_TICK_MS 10` ✓。 并行勘查会话的同族发现(它扫「文档声明数 vs 真值」,逐处独立复验后采纳 3 处) - `App/protocol/iec104/iec104_link.h` 头注释写「端口:TCP 2404(§1.5 B0 定义)」, 同文件下方已写「服务端口真源已迁 [comm] 段」= 自相矛盾 → 补「工厂默认」并指回 [comm] 与「下次重 bind 即生效」。 - `scripts/gen_task_table.py` 的 iec104Task 描述「:2404 监听」→「工厂默认 :2404 监听」,`--write` 重生成 README 任务表(AGENTS 表无描述列,实测未被改写)。 - `App/log/README.md` 五行写入点行号全漂:事故 181→188、告警 184→191、启动 176→183(`App/task/protect_task.c`);操作 `iec104_pts.c` 574/602→582/610、 `default_task.c` 242→266、`sys_log_mcu.c` 646→682;遥控 573/576/582/586/596/ 598/609 → 581/584/590/594/604/606/617(宿主 `sim_syslog.c:245` 原值正确、未动)。 - **撤销一条**:我早先记的「`docs/算法/继电保护功能复现规划.md:225` 声称记录本已 有消费者」与原文不符——:225 实为规划表里「动作/SOE 记录 | 复用 `sys_log`」一行, 属能力复用口径、不是当前态断言 → 不改。教训:**改引用前逐处读现文,别照自己 上一轮的笔记盲替**(这条比改中的每一处都值钱)。 - 同一批勘查还报了约 12 处「`2404` 缺『工厂默认』限定词」(AGENTS、`App/protocol/ README.md`、`Src/freertos.c` 注释、5 篇 docs、`test/host` 工具,含一张配图生成器 要连产物同批重跑)与 `测试开发标准流程.md`「像素层 60 内容页」、 `.pre-commit-config.yaml` uinav 注释「快检档 18 张黄金参考」(后者写时为真、 描述的却是钩子现有行为):**本笔刻意不做**,另开任务空间处理——那批要同时动 AGENTS.md 与 `.pre-commit-config.yaml` 两处单写者面,与并行会话撞车的代价更高。 - 端口生效语义(用户裁定 2026-10-30):产品基线写「公共地址与端口可配…改后需重启 生效」,代码 `App/task/iec104_serv.c` 是「ca 改后要重启;port 下次重 bind 才生效」 ——裁定**按代码语义要改**处理(改了端口应立刻重 bind),属固件行为变更,另开任务 且需真机验证;`.serialtest/config.json` 写死 `i104: listening on 2404` 必检锚点 是同题(现场改端口会让串口回归假失败),一并留给那条任务。 自查(任务树内) - `check_doc_links.py --sections-strict`:12 个改动 .md 逐个 rc=0; `check_mermaid.py` 改动 .md 全 rc=0;`check_generated_fresh.py` PASS (25 个生成器 / 21 一致 / 4 存量基线);`check_nested_comment.py --all` PASS (449 文件,新发 0、存量 2);`check_bom_drift.py --all` 99 个名单文件无漂移。 - 编译:`.dsh/skills/keil-build` 退出码 0(0 错误 0 警告), Program Size Code=246720 RO=29836 RW=2052 ZI=149708 —— 与本树凭证同码, 证明这批改的确实只有注释与文档。 - 字节体检:23 个文件无 CR / U+FFFD / 行尾空格 / 缺末行换行;`ui_task.c` 被编辑后 丢的 3 字节 BOM 已补回(bom-guard 复验通过);5 处「三连空行」逐文件与 HEAD 比对确认是存量(Python 生成器的 PEP8 双空行与个别 C 文件原有空行)。 - 一次性工具 `ci-artifacts/fix_docs2.py`:断言式规则表,按文件读一次、在演化文本上 逐条替换、一次写盘。第一版写成「每条规则各自从启动时快照起算」,同一文件后一条 把前一条整体覆盖,只有每文件最后一条活下来——靠「改完再全仓复扫旧形态」当场抓到, 这条判据本身已值得写进流程。 - 待办:`git add` 后需重跑 `ram_baseline.py --update`(体积凭证按 git 索引内容生成, 未 staged 的文件不在凭证里,`check_size_gate.py --local` 现在因此报红)。 - 未 commit、未 push(AGENTS.md 边界:提交属 Ask first)。
119 lines
9.3 KiB
Markdown
119 lines
9.3 KiB
Markdown
# App/log/ — 日志子系统
|
||
|
||
> 两类日志:**持久化运行日志**(`sys_log`,落 NAND)与**控制台日志**(`console_log`,走串口)。
|
||
> 两者都遵循**单写者**模型,避免多任务交错输出。
|
||
|
||
## 文件一览
|
||
|
||
| 文件 | 作用 |
|
||
|---|---|
|
||
| `sys_log.h` | **日志接口(平台无关)**:定长 64B 记录,七类各一个 `/log` 文件(run / selftest / accid / alarm / start / oper / yk)|
|
||
| `sys_log_mcu.c` | `sys_log` 的 MCU 端后端实现(NAND + FatFs) |
|
||
| `console_log.c/h` | **控制台单写者日志**:多任务日志经队列汇入唯一写者,避免串口输出交错 |
|
||
| `soe_rec.c/h` | **SOE 记录纯编解码**(B9):64B 定长记录(CP56Time2a 时标 + 类型 + 元件号 + 单调序号 + 值 + sum8 撕裂校验),零 I/O 零 OS 依赖,主机单测覆盖 |
|
||
| `soe_store.c/h` | **SOE 落盘胶水**(B9):`/log/soe.log`,protectTask 投递(零文件系统)→ defaultTask 排空落盘 |
|
||
|
||
## 关键机制
|
||
|
||
### syslog(运行/自检记录)
|
||
|
||
- **唯一写者 = defaultTask**;其它任务只投递,不直接写盘
|
||
- **单记录定长 64 B**,循环写;待机页可显示记录
|
||
- **内核启动前的日志**走**无锁静态缓冲** `s_preboot[8]` 暂存(因为内核启动前不能建队列),
|
||
内核启动后由 `syslog_drain` 惰性创建真队列并排空
|
||
- ⚠️ 写盘必须校验 `f_write` 返回的**字节数**(该 FatFs 移植在簇分配失败时返回 `FR_OK` + 短写,
|
||
不报错误码);追加写必须 `FA_WRITE|FA_OPEN_APPEND`(`FA_OPEN_APPEND` 单独用**不含写位**)
|
||
- **清除记录 = 请求 / 落地分离**:非写者上下文(uiTask 等)只能 `syslog_request_clear(id)`
|
||
置一个标志(零盘触点、零卷锁),真正的 `f_unlink` 在 **defaultTask** 的 `syslog_drain()`
|
||
**尾部**执行——顺序也是判据的一部分:排在条目排空之后,否则本轮刚排空的旧记录会被
|
||
写回去、`syslog_count()` 不归零。结果读 `syslog_clear_state(id)`
|
||
(`NONE/OK/WAIT/FAIL`),事实复核读 `syslog_count()`。
|
||
落地函数 `syslog_clear_apply(id)` 是 MCU 后端专属(模拟器不实现 → 共享代码误调在
|
||
链接期就报错):卷锁一律 try,卷占用返 -1 且**不动原文件**;幂等只认 `FR_NO_FILE`,
|
||
`FR_NO_PATH` 如实返 -1 且**不做任何卷级动作**(不补目录、不重格式化)
|
||
- 删除的作用域 = 上表七个记录文件(`syslog_request_clear(id)` 逐 id 独立请求);取证文件
|
||
`wdev.log` 与 `soe.log` 各有各的属主与通道,不在本 API 作用域,想清它们要另开 API。
|
||
清除成功会往**操作记录**里落一条 `clear <文件>`——但清 `oper.log` 自己时**刻意不投**,
|
||
否则"我清除了记录"这条会把刚删空的文件又写成 1 条,直接砸掉「删除后 `syslog_count()`
|
||
归零」这条判据(`TC-SYSLOG-133b` 钉住)
|
||
- ⚠ 宿主后端(`simulator/sim/sim_syslog.c`)与 MCU 端**判据相同、时序不同**:模拟器单线程,
|
||
七类对应工作目录下七份宿主文件(`sim_accid.log` 等),`syslog_post_quiet()` 在本端
|
||
等价于 `syslog_post()`(宿主进程没有那条栈硬线,也没有队列与丢弃);
|
||
`syslog_request_clear()` 调用内就删完了,因此 `WAIT` 态在模拟器上永不出现
|
||
(同 `syslog_post` 在两端"有队列 / 直写无队列"的差异)
|
||
|
||
### 五类记录与「记录类型 = 日志 ID」(道F task-10,2026-10-26;task-11 记录 5 页按此做,2026-10-28 五页已落地)
|
||
|
||
**决策一:记录类型就是 `syslog_id_t` 本身,一类记录 = `/log` 下一个严格 8.3 短名的文件。**
|
||
64B 定长记录体里**没有类型字段位**:`[0..18]` 时标 / `[19]` 空格 / `[20..29]` epoch /
|
||
`[30]` 空格 / `[31..58]` 描述 / 尾部补零,这个布局被 UI 记录页与主机解码工具按**固定偏移**
|
||
读了了很久(run.log / selftest.log 是既有文件,既有读者)。塞类型字节要么挪走 epoch
|
||
(上电/重启分类就断),要么挪走描述(屏上少字),且两端读者同批改——所以
|
||
"这条记录是什么类型"由**它在哪个文件里**回答。`TC-SYSLOG-136` 把这条钉成机器断言:
|
||
同一条内容在七类之间逐字节相同,记录体不编码类型。
|
||
|
||
**决策二:不动 `soe_type_t`(二进制 SOE 通道保持现有 8 型)。** 加 SOE 类型必须同步 UI 侧
|
||
`ui_w_soe.c` 的类型名表(那是另一位队友的写域),而 SOE 页与这五页本就是两套读者
|
||
(二进制解码器 vs 文本列表)。**别把新类型顺手加进 SOE**:那会同时造出
|
||
"同一个事件两个真源"。
|
||
|
||
| 记录 | 日志 ID / 文件 | 容量上限 | 写入点 | 触发判据 |
|
||
|---|---|---|---|---|
|
||
| 事故 | `SYSLOG_ACCIDENT` / `accid.log` | 512 | `App/task/protect_task.c:188` | `asserted` 上升沿**且**该元件跳闸投退项 = 1 |
|
||
| 保护告警 | `SYSLOG_ALARM` / `alarm.log` | 1024 | 同文件 `191` | `asserted` 上升沿**且**跳闸投退项 = 0 或该元件无此项 |
|
||
| 保护启动 | `SYSLOG_START` / `start.log` | 1024 | 同文件 `183` | `picked` 上升沿(越限电平,早于/等于动作) |
|
||
| 操作 | `SYSLOG_OPER` / `oper.log` | 512 | `App/protocol/iec104/iec104_pts.c:582/610`、`App/task/default_task.c:266`、`App/log/sys_log_mcu.c:682`(宿主 `sim_syslog.c:245`) | 强制复归受理 / 软压板投退落地 / 定值**真落盘成功** / 清除记录成功 |
|
||
| 遥控 | `SYSLOG_YK` / `yk.log` | 256 | `App/protocol/iec104/iec104_pts.c:581/584/590/594/604/606/617` | 104 双点命令的选择段、执行段、非法 DPC、未知 IOA 各一条 |
|
||
|
||
- **"哪些元件算跳闸"只有一个真源**:定值表 `g_prot_set_meta` 里的 `PS_*_TRIP`(枚举注释
|
||
即"0 告警 / 1 跳闸")+ 实例表 `k_pe[]` 的 `trip_ctl` 列。分类入口
|
||
`prot_elm_record_kind()`(`App/algo/prot_elm.c`)里**没有第二份元件名单**,
|
||
没有该投退项的五项(OV/UV/3U0/OF/UF)填 `PROT_ELM_NO_TRIP=0xFF` → 恒告警;
|
||
`TC-PROTELEM-015` 逐元钉住"逐项独立 / 改定值即改类别 / 无此项者不受别人定值影响"。
|
||
- **描述只用 ROM 常量或 `prot_elm_name()` 直传**:所有写入点零 `snprintf`、零拼接。
|
||
最长一条 20 字节 < `SYSLOG_DESC_MAX`(28);`TC-IEC104-030` 逐条量长度(超 28 会被
|
||
投递侧静默截断,屏上少几个字,没人会去数)。
|
||
- ⚠️ **两个投递口,按调用者的栈预算选**:`syslog_post()` 的两个"满态丢弃留痕"分支
|
||
内联着日志行缓冲(每处约 216 B 栈),因此 `syslog_post_quiet()` 给静态最坏链吃紧的
|
||
任务用(**protectTask、iec104Task 必须用它**),丢弃只累加 `syslog_post_dropped()`、
|
||
由唯一写者 defaultTask 在 `syslog_drain()` 里按差值补一条留痕——同 `soe_store_post`
|
||
当年那条 `872 → 1192 B FAIL` 的实证纪律。收/丢判据两个口完全一致。
|
||
- **读侧契约(给 task-11;2026-10-28 五页已按此接上)**:`syslog_count(id)` + `syslog_get_record(id, idx, ts, ts_len,
|
||
desc, desc_len)` 与运行/自检记录**同一枚 reader**,"最近 20 条"= 从 `count-20` 起读;
|
||
`count()==0` 就是空态,页面显示"无记录"。**不要**在共享代码里调 MCU 专属的
|
||
`syslog_clear_apply()`(模拟器不实现 → 链接期报错是故意的);清除一律走
|
||
`syslog_request_clear()` + `syslog_clear_state()`,并把 `WAIT` 当正常态看(模拟器端
|
||
永不出现 `WAIT`,见下条)。
|
||
|
||
### console_log(控制台单写者)
|
||
|
||
- 多任务的生产者 → **单写者消费者**,消除任务间日志行交错
|
||
- 设计详见 `docs/子系统设计/控制台单写者日志设计说明.md`
|
||
|
||
### soe(SOE 事件顺序记录,B9)
|
||
|
||
- **记录 = 64 B 定长二进制**(`soe_rec.h` 布局),落 `/log/soe.log`;
|
||
与 run.log 分文件——run.log 是文本记录、有 UI/主机读取方,二进制 SOE
|
||
混入会破坏读取器;分文件保持"定长 64B + 条数=fsize/64 + 满删重建 +
|
||
FTP 下载 + 主机解码"同一套先例
|
||
- **生产者 = protectTask**(元件动作/返回、软压板投退、采样闭锁/恢复六类
|
||
边沿):`soe_store_post()` 纯 RAM(取时标 + 组记录 + 队列/待发环),
|
||
**零 NAND I/O**;⚠️ **post 内禁 `DBG_*`**——会把 protectTask 静态最坏链
|
||
垫高一帧(实证 872→1192 B FAIL),丢弃留痕由 drain(defaultTask)差值上报
|
||
- **唯一写者 = defaultTask**:挂 `syslog_drain()` 尾部(继承其卷就绪门),
|
||
50 ms 节拍 → SOE 落盘延迟 ≤50 ms;写盘纪律全套照 syslog(卷锁 try /
|
||
`FA_WRITE|FA_OPEN_APPEND` / 校验字节数 / 满删重建 / 失败留痕去重),
|
||
**绝不把名字/参数类错误码归入卷损坏触发集**
|
||
- **时标口径**:sys_clock 秒域时基 → CP56 ms 域 = 秒×1000(亚秒恒 0,
|
||
不造假);墙钟未建 → IV 位如实置位;seq 仅上电生命周期内单调
|
||
(容量重建不重置,仅复位重置——主机解码按 boot 段分段断言)
|
||
- 主机侧:`test/host/soe/decode_soe.py` 解码 + 断言(校验和/seq 单调/
|
||
CP56↔epoch UTC 交叉);⚠️ 交叉校验用 `calendar.timegm`,不是本地时区的
|
||
`dt.timestamp()`(+08 机差 28800 s,首跑实证坑)
|
||
|
||
## 相关文档
|
||
|
||
- `docs/子系统设计/控制台单写者日志设计说明.md`
|
||
- `docs/子系统设计/运行与自检记录设计说明.md`
|
||
- FatFs 陷阱:`AGENTS.md`「平台约束」
|