Files
STM32F4-Base/tools/arch_diagram/gen_barrier_svg.py
T
wandering1 e4d70f9fcb docs(真值): 勘查 13 条文档/代码不一致全部处置(10 改 3 核实)+ 补 3 处
证据源 = 主树 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)。
2026-10-30 14:19:35 +08:00

248 lines
11 KiB
Python
Raw 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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成 docs/images/就绪屏障.svg —— 启动就绪屏障(Event Flags)依赖图(纯标准库)。
为什么不用 Mermaid:原图 9 条边全挤在单一中心节点上,画成交叉的"意大利面"。
本脚本手工排布为三栏:
左 = 生产者(set) 中 = 事件组 sys_evtHandle 右 = 消费者(wait)
任务卡与事件位各自对齐到同一水平行,箭头基本水平/正交;bit1 → ftpTask 的
"与"依赖用汇合点(All)表达,避免跨行连线。
用法(仓库根目录,任意 python3):
python tools/arch_diagram/gen_barrier_svg.py
输出:
docs/images/就绪屏障.svg
改依赖时:编辑本文件顶部的 BITS / PRODUCERS / CONSUMERS 数据后重跑。
"""
import html
import math
import os
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
OUT = os.path.join(ROOT, "docs", "images", "就绪屏障.svg")
# ----------------------------- 画布与尺寸 -----------------------------
W = 1400
PAD = 40
TITLE_FONT = 20
SUB_FONT = 13
CARD_TITLE_FONT = 15
CARD_NOTE_FONT = 12
BIT_TITLE_FONT = 14
BIT_NOTE_FONT = 12
LABEL_FONT = 11.5
FOOT_FONT = 12
H_CARD = 84 # 任务卡 / 事件位行高
GAP = 26 # 行间距
MAIN_Y = 200 # 首行 y
CARD_W_L = 290 # 生产者卡宽
CARD_W_R = 450 # 消费者卡宽
PANEL_X, PANEL_W = 400, 420
PROD_X = 40
CONS_X = 910
TAP_X = 832 # bit1 → All 汇合点的下引线 x
BUS_X = 862 # bit0 分发总线 x
ARROW_W = 2.2
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
C_PROD, BG_PROD = "#2563eb", "#eff6ff"
C_CONS, BG_CONS = "#7c3aed", "#f5f3ff"
# ----------------------------- 数据(改这里) -----------------------------
# 事件位(中间栏),自上而下 = 时序先后
BITS = [
dict(bit="bit1", flag="STORAGE_READY", color="#16a34a", bg="#f0fdf4",
note="defaultTask 置位(挂载+配置)· netTask / ftpTask 等待"),
dict(bit="bit0", flag="NET_READY", color="#0d9488", bg="#f0fdfa",
note="netTask 置位 · 5 个任务等待"),
dict(bit="bit2", flag="FTP_READY", color="#d97706", bg="#fffbeb",
note="ftpTask 置位 · 预留消费者"),
dict(bit="bit3", flag="TIMESYNC_READY", color="#94a3b8", bg="#f8fafc",
note="预留(无生产者置位)"),
]
# 生产者(左栏):row = 其置位的 bit 行
PRODUCERS = [
dict(name="defaultTask", note="卷挂载 + net_cfg_load(f_mount)", row=0, label="set bit1"),
dict(name="netTask", note="net_stack_bring_up()", row=1, label="set bit0"),
dict(name="ftpTask", note='lftpd_start("/", 21)', row=2, label="set bit2"),
]
# 消费者(右栏):row = 卡片排布行(前两行与 bit1 / bit0 对齐)
CONSUMERS = [
dict(name="netTask", note="wait bit1 · ≤10s", row=0),
dict(name="ftpTask", note="wait bit0 + bit1 · All", row=1),
dict(name="uiTask", note="wait bit0 · Forever", row=2),
dict(name="timeSyncTask", note="wait bit0 · Forever", row=3),
dict(name="iec104Task", note="wait bit0 · Forever", row=4),
dict(name="testTask", note="wait bit0 · NET 模式", row=5),
]
FOOT = ("生产者与消费者互不知晓,只经事件组解耦;新增子系统 = 新 bit + 各自 wait/set,"
"不改已有任务。详见 docs/架构设计/项目启动顺序管理.md")
# ----------------------------- 工具函数 -----------------------------
def esc(s):
return html.escape(s, quote=True)
def text_w(s, fs):
"""粗略估算文本像素宽:CJK/全角 ≈ 1.0em,其余 ≈ 0.58em(宁可偏大)。"""
w = 0.0
for ch in s:
w += fs * (1.0 if ord(ch) >= 0x2E80 else 0.58)
return w
# ----------------------------- 生成 -----------------------------
def build():
out = []
add = out.append
def row_y(r):
return MAIN_Y + r * (H_CARD + GAP)
def row_cy(r):
return row_y(r) + H_CARD / 2.0
def poly(points, color, dash=False, head=True, width=ARROW_W):
pts = " ".join("%.1f,%.1f" % p for p in points)
d = ' stroke-dasharray="7 5"' if dash else ''
add('<polyline points="%s" fill="none" stroke="%s" stroke-width="%.1f" '
'stroke-linejoin="round"%s/>' % (pts, color, width, d))
if head:
(x1, y1), (x2, y2) = points[-2], points[-1]
ang = math.atan2(y2 - y1, x2 - x1)
hl, hw = 11.0, 5.5
bx, by = x2 - hl * math.cos(ang), y2 - hl * math.sin(ang)
px, py = -math.sin(ang) * hw, math.cos(ang) * hw
add('<path d="M %.1f %.1f L %.1f %.1f L %.1f %.1f z" fill="%s"/>'
% (x2, y2, bx + px, by + py, bx - px, by - py, color))
def card(x, y, w, title, note, color, bg):
add('<rect x="%d" y="%d" width="%d" height="%d" rx="10" fill="%s" '
'stroke="%s" stroke-opacity="0.5"/>' % (x, y, w, H_CARD, bg, color))
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
% (x + 16, y + 34, CARD_TITLE_FONT, esc(title)))
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
% (x + 16, y + 58, CARD_NOTE_FONT, esc(note)))
# 行/画布尺寸
n_rows = max(len(CONSUMERS), len(BITS))
main_bottom = row_y(n_rows - 1) + H_CARD
total_h = main_bottom + 78
add('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, total_h))
# ---- 标题 ----
add('<text x="%d" y="42" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
% (PAD, TITLE_FONT, esc("就绪屏障(Readiness Barrier)· 启动依赖链")))
add('<text x="%d" y="68" font-size="%d" fill="#64748b">%s</text>'
% (PAD, SUB_FONT,
esc("CMSIS-RTOS2 Event Flags · 生产者置位声明「我好了」,消费者阻塞等待")))
# ---- 图例 ----
lx, ly = PAD, 108
for kind, color, label in [
("rect", C_PROD, "生产者(set)"),
("rect", C_CONS, "消费者(wait)"),
("rect", "#16a34a", "事件位(Event Flag)"),
("dot", "#0d9488", "All 汇合(等全部位)"),
("dash", "#94a3b8", "预留 / 未接入"),
]:
if kind == "rect":
add('<rect x="%d" y="%d" width="16" height="16" rx="4" fill="#ffffff" '
'stroke="%s" stroke-width="2"/>' % (lx, ly - 12, color))
elif kind == "dot":
add('<circle cx="%d" cy="%d" r="6" fill="%s"/>' % (lx + 8, ly - 4, color))
else:
add('<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="%s" stroke-width="2.4" '
'stroke-dasharray="7 5"/>' % (lx, ly - 4, lx + 16, ly - 4, color))
lx += 24
add('<text x="%d" y="%d" font-size="12" fill="#475569">%s</text>' % (lx, ly, esc(label)))
lx += text_w(label, 12) + 34
# ---- 事件组面板 ----
py0 = MAIN_Y - 34
py1 = row_y(len(BITS) - 1) + H_CARD + 14
add('<rect x="%d" y="%d" width="%d" height="%d" rx="14" fill="#f8fafc" '
'stroke="#64748b" stroke-opacity="0.35"/>' % (PANEL_X, py0, PANEL_W, py1 - py0))
head = "sys_evtHandle(Event Flags 事件组)"
add('<text x="%d" y="%d" font-size="14" font-weight="700" fill="#334155">%s</text>'
% (PANEL_X + 16, py0 + 26, esc(head)))
for i, b in enumerate(BITS):
by = row_y(i)
add('<rect x="%d" y="%d" width="%d" height="%d" rx="10" fill="%s" stroke="%s" '
'stroke-opacity="0.6"/>' % (PANEL_X + 14, by, PANEL_W - 28, H_CARD, b["bg"], b["color"]))
add('<rect x="%d" y="%d" width="30" height="30" rx="7" fill="%s"/>'
% (PANEL_X + 28, by + (H_CARD - 30) / 2, b["color"]))
add('<text x="%d" y="%d" font-size="14" font-weight="700" fill="#ffffff" '
'text-anchor="middle">%s</text>'
% (PANEL_X + 43, by + H_CARD / 2 + 5, esc(b["bit"][3])))
tx = PANEL_X + 70
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="%s">%s</text>'
% (tx, by + 36, BIT_TITLE_FONT, b["color"], esc(b["bit"] + " · " + b["flag"])))
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
% (tx, by + 58, BIT_NOTE_FONT, esc(b["note"])))
# ---- 生产者卡 + set 箭头 ----
for p in PRODUCERS:
y, cy = row_y(p["row"]), row_cy(p["row"])
card(PROD_X, y, CARD_W_L, p["name"], p["note"], C_PROD, BG_PROD)
poly([(PROD_X + CARD_W_L, cy), (PANEL_X, cy)], BITS[p["row"]]["color"])
add('<text x="%.0f" y="%.1f" font-size="%s" fill="%s" text-anchor="middle">%s</text>'
% ((PROD_X + CARD_W_L + PANEL_X) / 2.0, cy - 8, LABEL_FONT,
BITS[p["row"]]["color"], esc(p["label"])))
# ---- 消费者卡 ----
for c in CONSUMERS:
card(CONS_X, row_y(c["row"]), CARD_W_R, c["name"], c["note"], C_CONS, BG_CONS)
# ---- wait 连线:bit1 → netTask(水平) ----
c0, c1 = row_cy(0), row_cy(1)
col1, col0 = BITS[0]["color"], BITS[1]["color"]
poly([(PANEL_X + PANEL_W, c0), (CONS_X, c0)], col1)
add('<text x="%.0f" y="%.1f" font-size="%s" fill="%s" text-anchor="middle">%s</text>'
% ((PANEL_X + PANEL_W + CONS_X) / 2.0, c0 - 8, LABEL_FONT, col1, esc("wait")))
# ---- wait 连线:bit1 → All 汇合点(下引,与 bit0 相与) ----
poly([(TAP_X, c0), (TAP_X, c1)], col1, head=False)
add('<circle cx="%d" cy="%.1f" r="5.5" fill="%s"/>' % (TAP_X, c1, col0))
add('<text x="%d" y="%.1f" font-size="%s" font-weight="700" fill="%s">All</text>'
% (TAP_X + 9, c1 - 8, LABEL_FONT, col0))
# ---- wait 连线:bit0 → 总线 → 各 bit0 消费者(ftpTask/ui/timeSync/iec104/testTask) ----
c_last = row_cy(len(CONSUMERS) - 1)
poly([(PANEL_X + PANEL_W, c1), (BUS_X, c1)], col0)
poly([(BUS_X, c1), (BUS_X, c_last)], col0, head=False)
for c in CONSUMERS[1:]:
poly([(BUS_X, row_cy(c["row"])), (CONS_X, row_cy(c["row"]))], col0)
mid_y = (c1 + c_last) / 2.0
add('<text transform="rotate(-90 %.1f %.1f)" x="%.1f" y="%.1f" font-size="%s" '
'fill="%s" text-anchor="middle">%s</text>'
% (BUS_X - 12, mid_y, BUS_X - 12, mid_y, LABEL_FONT, col0, esc("wait bit0")))
add('<text x="%d" y="%d" font-size="%d" fill="#94a3b8">%s</text>'
% (PAD, total_h - 18, FOOT_FONT, esc(FOOT)))
svg = ('<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" '
'viewBox="0 0 %d %d" font-family="%s">\n%s\n</svg>\n'
% (W, total_h, W, total_h, FONT, "\n".join(out)))
return svg, total_h
def main():
svg, h = build()
with open(OUT, "w", encoding="utf-8", newline="\n") as f:
f.write(svg)
import xml.etree.ElementTree as ET
ET.fromstring(svg)
print("OK %d bytes, canvas %dx%d -> %s" % (len(svg.encode("utf-8")), W, h, OUT))
if __name__ == "__main__":
main()