通用配置文件 P1–P3:设计文档、FS 并发 A+ 定案、FatFs 墙钟接入、cfg_file 双端引擎与 net_cfg 集成(旧档 NCFG 首启迁移)
This commit is contained in:
@@ -53,7 +53,7 @@ L_UI = dict(
|
||||
)
|
||||
L_SVC = dict(
|
||||
title="③ 基础服务层", sub="应用侧服务 / 编排", color="#0d9488", bg="#f0fdfa",
|
||||
chips=["app_main(启动编排)", "sys_clock(墙钟基准)", "sys_events(就绪屏障)",
|
||||
chips=["app_main(启动编排)", "sys_events(就绪屏障)",
|
||||
"app_cfg(编译开关)", "task/(net · ui · adc* · rs485*)",
|
||||
"lftpd(FTP :21)", "time_sync(TCP 对时 :8888)"],
|
||||
)
|
||||
@@ -61,7 +61,7 @@ L_MW = dict(
|
||||
title="④ 中间件层", sub="通用库 / 协议栈", color="#16a34a", bg="#f0fdf4",
|
||||
chips=["net_socket / net_select(BSD API)",
|
||||
"FatFs(文件系统)", "nand_ftl(dhara FTL)", "LVGL v9.2(图形库)",
|
||||
"util(crc · ringbuf)"],
|
||||
"sys_clock(墙钟基准)", "util(crc · ringbuf)"],
|
||||
)
|
||||
L_DRV = dict(
|
||||
title="⑤ 驱动层", sub="Drivers/BSP · 芯片时序/中断", color="#d97706", bg="#fffbeb",
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""生成 docs/images/配置文件分层架构.svg —— 通用配置文件(/config.ini)分层架构图(纯标准库,无需第三方依赖)。
|
||||
|
||||
由 gen_net_stack_svg.py 复制改数据区:配置体系六层结构
|
||||
(docs/配置文件设计说明.md §4.1):
|
||||
PC 端(编辑器/FTP 客户端)→ lftpd FTP 服务 → /config.ini 文件 → cfg_file 引擎 → 段适配(键表)→ 消费者。
|
||||
|
||||
用法(仓库根目录,任意 python3):
|
||||
python tools/arch_diagram/gen_cfg_arch_svg.py
|
||||
输出:
|
||||
docs/images/配置文件分层架构.svg
|
||||
改架构时:编辑本文件顶部的 LAYERS / ROWS 数据后重跑即可。
|
||||
"""
|
||||
import html
|
||||
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 = 1200
|
||||
PAD = 26
|
||||
TITLE_FONT = 20
|
||||
SUB_FONT = 13
|
||||
BAND_TITLE_FONT = 17
|
||||
BAND_SUB_FONT = 12
|
||||
CHIP_FONT = 13
|
||||
|
||||
CHIP_H = 38 # 芯片盒高
|
||||
CHIP_GAP = 10 # 芯片水平间距
|
||||
CHIP_ROW_GAP = 10 # 芯片行间距
|
||||
CHIP_PAD = 16 # 芯片内左右留白
|
||||
BAND_PAD_X = 18 # 框内左右留白
|
||||
BAND_TITLE_H = 38 # 框内标题区高
|
||||
BAND_PAD_BOTTOM = 16
|
||||
BAND_H_GAP = 26 # 同一行内并列框的间距
|
||||
BAND_V_GAP = 44 # 行间距(放箭头)
|
||||
TITLE_H = 78 # 顶部标题区高
|
||||
|
||||
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
|
||||
|
||||
# ----------------------------- 架构数据(改这里) -----------------------------
|
||||
L_PC = dict(
|
||||
title="PC 端(外部)", sub="文本编辑器 + FTP 客户端", color="#475569", bg="#f1f5f9",
|
||||
chips=["文本编辑器:改 /config.ini(注释 / 加段 / 改值)",
|
||||
"FTP 客户端:RETR 下载 / STOR 上传(端口 21)"],
|
||||
)
|
||||
L_FTP = dict(
|
||||
title="lftpd FTP 服务(端口 21)", sub="ftpTask · 单连接 · root = /(= 卷根)", color="#7c3aed", bg="#f5f3ff",
|
||||
chips=["RETR / STOR / LIST / DELE / CWD(lftpd 现状能力)",
|
||||
"STOR = 原地覆盖(无 tmp+rename)",
|
||||
"路径规范化封死 .. 穿越"],
|
||||
)
|
||||
L_FILE = dict(
|
||||
title="/config.ini(通用配置文件)", sub="FatFS 卷根(NAND GD5F2GQ5UE · dhara FTL)", color="#0d9488", bg="#f0fdfa",
|
||||
chips=["INI 文本:[段] / key=value / ; 注释",
|
||||
"CRLF · UTF-8(容忍 BOM)· ≤ 4KB",
|
||||
"设备写:tmp + rename(原子);变更识别:size + mtime"],
|
||||
)
|
||||
L_ENGINE = dict(
|
||||
title="cfg_file(配置文件引擎)", sub="App/cfg_file.c · 双端共享", color="#2563eb", bg="#eff6ff",
|
||||
chips=["INI 解析 / 序列化(逐键校验 · 容错 · 自愈)",
|
||||
"外科手术式保存(保留注释 / 未知段 → 降级安全)",
|
||||
"2s 变更轮询(f_stat)+ 段变更回调",
|
||||
"出厂模板生成 · 旧 NCFG blob 迁移"],
|
||||
)
|
||||
L_SECT = dict(
|
||||
title="段适配(键表)", sub="段 RAM 副本 = 模块静态结构体(稳态 0 增量)", color="#16a34a", bg="#f0fdf4",
|
||||
chips=["net_cfg [network]:ip / mask / gateway / mac",
|
||||
"未来:[adc] · [rs485] · [ftp] ...(三步接入)"],
|
||||
)
|
||||
L_CONS = dict(
|
||||
title="消费者", sub="读 / 写段 RAM 副本 + 应用", color="#e11d48", bg="#fff1f2",
|
||||
chips=["netTask:组网 / 重建(代次号自愈)",
|
||||
"UI 网口设置页:显示 / 编辑 / 保存(零改动)",
|
||||
"未来模块:cfg_file_section_data() 取段数据"],
|
||||
)
|
||||
|
||||
# 每一行可放多个"同级并列"的层(上层只依赖下方各层)
|
||||
ROWS = [
|
||||
[L_PC],
|
||||
[L_FTP],
|
||||
[L_FILE],
|
||||
[L_ENGINE],
|
||||
[L_SECT],
|
||||
[L_CONS],
|
||||
]
|
||||
|
||||
FOOT = "稳态 RAM 零增量(段数据 = 模块既有结构体);暂态堆缓冲 ≤9KB(仅 load/save 时);FF_FS_REENTRANT=1 卷锁串行化多任务 FS 访问;模拟器端 = 宿主文件 sim_config.ini(同核心)"
|
||||
|
||||
|
||||
# ----------------------------- 工具函数 -----------------------------
|
||||
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) >= 0x2460 else 0.58)
|
||||
return w
|
||||
|
||||
|
||||
def chip_w(label):
|
||||
return text_w(label, CHIP_FONT) + 2 * CHIP_PAD
|
||||
|
||||
|
||||
def wrap_chips(chips, avail):
|
||||
rows, cur, cur_w = [], [], 0.0
|
||||
for c in chips:
|
||||
cw = chip_w(c)
|
||||
need = cw if not cur else cur_w + CHIP_GAP + cw
|
||||
if cur and need > avail:
|
||||
rows.append(cur)
|
||||
cur, cur_w = [c], cw
|
||||
else:
|
||||
cur.append(c)
|
||||
cur_w = need
|
||||
if cur:
|
||||
rows.append(cur)
|
||||
return rows
|
||||
|
||||
|
||||
# ----------------------------- 生成 -----------------------------
|
||||
def build():
|
||||
out = []
|
||||
add = out.append
|
||||
|
||||
# 布局:逐行排布(行内可并列多个框,行高取该行最高框)
|
||||
placements = [] # (layer, chip_rows, x, y, w, h)
|
||||
y = TITLE_H
|
||||
for row in ROWS:
|
||||
n = len(row)
|
||||
bw = (W - 2 * PAD - BAND_H_GAP * (n - 1)) // n
|
||||
bands = []
|
||||
for i, layer in enumerate(row):
|
||||
bx = PAD + i * (bw + BAND_H_GAP)
|
||||
avail = bw - 2 * BAND_PAD_X
|
||||
rows = wrap_chips(layer["chips"], avail)
|
||||
bh = BAND_TITLE_H + len(rows) * CHIP_H + (len(rows) - 1) * CHIP_ROW_GAP + BAND_PAD_BOTTOM
|
||||
bands.append([layer, rows, bx, y, bw, bh])
|
||||
row_h = max(b[5] for b in bands)
|
||||
for b in bands:
|
||||
b[5] = row_h # 同行等高(视觉"同级")
|
||||
placements.extend(bands)
|
||||
y += row_h + BAND_V_GAP
|
||||
total_h = y - BAND_V_GAP + 54
|
||||
|
||||
add('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, total_h))
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
|
||||
% (PAD, 40, TITLE_FONT, esc("通用配置文件分层架构(/config.ini)")))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (PAD, 62, SUB_FONT,
|
||||
esc("INI 文本文件 · FTP 上传/下载 · 段键表引擎 · MCU/模拟器双端共享")))
|
||||
|
||||
for (layer, rows, bx, by, bw, bh) in placements:
|
||||
# 框背景 + 边框
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="12" fill="%s" stroke="%s" '
|
||||
'stroke-opacity="0.45"/>' % (bx, by, bw, bh, layer["bg"], layer["color"]))
|
||||
# 标题(层名 + 目录/职责说明)
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="%s">%s</text>'
|
||||
% (bx + BAND_PAD_X, by + 26, BAND_TITLE_FONT, layer["color"], esc(layer["title"])))
|
||||
tw = text_w(layer["title"], BAND_TITLE_FONT)
|
||||
add('<text x="%.0f" y="%d" font-size="%s" fill="#64748b">%s</text>'
|
||||
% (bx + BAND_PAD_X + tw + 12, by + 26, BAND_SUB_FONT, esc(layer["sub"])))
|
||||
# 芯片
|
||||
cy = by + BAND_TITLE_H
|
||||
for row in rows:
|
||||
cx = bx + BAND_PAD_X
|
||||
for c in row:
|
||||
cw = chip_w(c)
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="8" fill="#ffffff" '
|
||||
'stroke="%s" stroke-opacity="0.55"/>' % (cx, cy, cw, CHIP_H, layer["color"]))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#1e293b" text-anchor="middle">%s</text>'
|
||||
% (cx + cw / 2, cy + CHIP_H / 2 + CHIP_FONT * 0.35, CHIP_FONT, esc(c)))
|
||||
cx += cw + CHIP_GAP
|
||||
cy += CHIP_H + CHIP_ROW_GAP
|
||||
|
||||
# 行间箭头:每个上层框向下引一条
|
||||
row_first = [] # 每行首个 placement 的下标
|
||||
idx = 0
|
||||
for row in ROWS:
|
||||
row_first.append(idx)
|
||||
idx += len(row)
|
||||
for r in range(len(ROWS) - 1):
|
||||
y0 = placements[row_first[r]][3] + placements[row_first[r]][5] + 6
|
||||
y1 = placements[row_first[r + 1]][3] - 8
|
||||
for i in range(row_first[r], row_first[r] + len(ROWS[r])):
|
||||
(_, _, bx, _, bw, _) = placements[i]
|
||||
ax = bx + bw / 2
|
||||
add('<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="#94a3b8" '
|
||||
'stroke-width="2.5"/>' % (ax, y0, ax, y1 - 7))
|
||||
add('<path d="M %.1f %.1f l -6 -9 h 12 z" fill="#94a3b8"/>' % (ax, y1))
|
||||
|
||||
add('<text x="%d" y="%d" font-size="12" fill="#94a3b8">%s</text>'
|
||||
% (PAD, total_h - 18, 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()
|
||||
Reference in New Issue
Block a user