Files
STM32F4-Base/tools/arch_diagram/gen_barrier_svg.py
T

247 lines
10 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 置位 · 4 个任务等待"),
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="FatFS 挂载(f_mount / f_mkfs)", 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="testTask", note="wait bit0 · NET 模式", row=4),
]
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 → 总线 → 4 个消费者 ----
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()