运行/自检记录与待机屏:sys_log定长64B日志引擎(/log/*.log,FTP可直下)+记录页x2+待机屏+密码错误提示;字体122字/217字形

This commit is contained in:
2026-09-14 19:05:00 +08:00
parent 0ec4737418
commit 5b93c6d296
37 changed files with 2101 additions and 109 deletions
@@ -55,7 +55,7 @@ scanned 13 files; unique non-ASCII chars: 78
-> D:\Code\DTU Code\STM32F4-Base\ui_cn_chars.txt
```
产物 `ui_cn_chars.txt` 为本工程实际显示的汉字清单(当前 111 字,含 ▶/,等符号),可留作字符集记录。
产物 `ui_cn_chars.txt` 为本工程实际显示的汉字清单(当前 122 字,含 ▶/,等符号),可留作字符集记录。
---
@@ -152,7 +152,7 @@ OK 43614 bytes -> ...\Drivers\BSP\UI\ui_font_cn_12.c
1. **1bpp 细笔画发虚**:宋体在 1bpp 下可能丢像素。若上板/客户端中文偏淡,把第 4 节 `--bpp` 改为 `2` 重生成即可(子集小,体积可接受)。
2. **字符集变更**:以后新增中文菜单项,须重跑第 3、4 节(重新提取 `ui_cn_chars.txt` 并生成),否则新字显示为方框。
3. **编码**:UI 源文件须为 **UTF-8(带 BOM)**,LVGL 按 UTF-8 解码中文串;字体 `.c` 为纯 ASCII,无编码风险。当前 Keil 已加 Misc Controls 抑制项,无需额外 `--locale=english`。
4. **字体体积**:12px + 1bpp + 111 字(含 ASCII/符号)≈ 43.6KB(RO-data);改 bpp 2 约翻倍,仍在 F407 Flash 余量内。
4. **字体体积**:12px + 1bpp + 122 字(含 ASCII/符号,共 217 字形)≈ 44.4KB(RO-data);改 bpp 2 约翻倍,仍在 F407 Flash 余量内。
5. **换字体**:黑体用 `simhei.ttf`(单文件免抽取);其余字体同理替换 `--font` 路径后重生成。
---
+2 -1
View File
@@ -48,7 +48,7 @@ flowchart TD
|---|---|---|---|
| C1 | 屏幕 | 160×160,1bpp 单色(`LV_COLOR_FORMAT_I1`),无物理屏(远端客户端显示) | `ui_port.h` |
| C2 | 字体 | **唯一**:`ui_font_cn_12`(宋体 12px 1bpp)。汉字 12px/字,ASCII 6px/字,行高 13px。需要其他字号 = 大工程(新字体 + 双端 `lv_conf.h`),默认禁止 | `ui_font_cn_12.c` |
| C3 | 字符集 | 子集字体(当前 111 汉字/符号 + ASCII 0x20-0x7E,共 208 字形);**缺字显示空白**;新字必须重生成字体 | `tools/font_gen/` |
| C3 | 字符集 | 子集字体(当前 122 汉字/符号 + ASCII 0x20-0x7E,共 217 字形);**缺字显示空白**;新字必须重生成字体 | `tools/font_gen/` |
| C4 | LVGL 内存池 | 40KB(`LV_MEM_SIZE`);任一时刻只放"当前导航路径 + 当前内容页" | `Lib/lv_conf.h` |
| C5 | 文本 | 一律静态字符串(`lv_label_set_text_static`),禁止动态字符串/strdup | 架构文档 |
| C6 | 编码 | `Drivers/BSP/UI/*`、`ui_task.c`、`Lib/lv_conf.h` 为 **UTF-8 带 BOM**;改动只用 Edit 工具,**禁止** PowerShell/cmd 整文件重写(U+FFFD 不可逆) | AGENTS.md 铁律 |
@@ -411,3 +411,4 @@ P4 [ ] UI_设计架构.md 变更记录更新
| 2026-09-09 | 用户密钥密码页落地后回填:C3 字符集 97→102 汉字(198 字形,补"本/用/户/密/钥");新文案走 P4 字体重生成 + `check_content_pages.py` 新增密码页 4 状态黄金参考 |
| 2026-09-10 | 网口设置内容页落地后回填:C3 字符集 102→111 字符(208 字形,补网口设置/地址/掩码等,并修复 `lv_font_conv` 丢弃的低码位符号 U+FF0C 全角逗号 → 页脚逗号不再空白);新文案走 P4 字体重生成 + `check_content_pages.py` 新增网口设置 4 状态黄金参考(可编辑/只读/编辑光标反显/保存 OK);模拟器 `main` 调 `net_cfg_load` 使参考图显示工厂默认值 |
| 2026-09-13 | 移除 HTML 样机阶段:五阶段 → **四阶段管线**(P0 需求录入+设计规格 / P1 规格→代码 / P2 模拟器 / P3 MCU / P4 归档);设计基准改为"规格布局参数表 + 参考图",P2 比对基准由"样机渲染图"改为"参考图 + 规格参数表"(根因分流:修代码 / 回 P0 修规格);用户视觉确认点由 HTML 浏览器移到 P2 模拟器窗口/出图;删除 `mockups/01_主界面.html`、`render_01.png`、`tools/ui_mock/render_mockup.py`,保留 `NN_说明.md` 规格与原始参考图;`NN_说明.md` 升格为唯一设计基准(P1 照参数表翻译代码,P2 出图对照参数表 + 原图);`UI_自动化测试流程.md` 门禁编号同步改为 P2/P3 |
| 2026-09-14 | 运行记录/自检记录页 + 待机屏落地后回填:C3 字符集 111→122 字(217 字形,补输/入/错/误/上/电/重/芯/片/异/常——**故障描述串在非 UI 源码中 extract_cn 扫不到,须手动补字**);新文案走 P4 字体重生成 + `check_content_pages.py` pwc 态参考更新(密码错误提示);池基线 4784→5440 / 往返 4864→5520(+656B = 待机模块常驻屏+容器,见两测试脚本头部注记) |
+2 -1
View File
@@ -37,7 +37,7 @@ MCU **无物理屏幕**:LVGL 做 160×160 **1bpp 单色离屏渲染**,帧缓
| LVGL 内存池 | 40 KB(`LV_MEM_SIZE`) | `Lib/lv_conf.h` |
| 刷新周期 | 10 ms(`ui_port_step(10)` + `osDelay(10)`) | `ui_task.c` |
| 推流端口 | 7003(TCP,与 DTU-RemoteLCD 客户端对齐) | `ui_net_sink.h` |
| 默认字体 | `ui_font_cn_12`(12px 1bpp,111 汉字/符号 + ASCII 0x20–0x7E,共 208 字形) | `Lib/lv_conf.h` |
| 默认字体 | `ui_font_cn_12`(12px 1bpp,122 汉字/符号 + ASCII 0x20–0x7E,共 217 字形) | `Lib/lv_conf.h` |
| uiTask 栈 | 16384 words(BelowNormal) | `Src/freertos.c` |
### 1.4 硬约束(改 UI 必须遵守)
@@ -260,3 +260,4 @@ ENTER/→ 进子框:
| 2026-09-09 | 内容页拆分执行(`内容页架构.md` §九 变更记录):`ui_content_page` 提纯为纯框架(描述符表 + NULL 防护 + 描述符驱动绘制/按键、4 个扩展钩子);三页数据拆出 `ui_page_device_info` / `ui_page_ac` / `ui_page_dc`(extern const 描述符对象);双端注册;Keil 0 错 0 警 + 模拟器 0 警告 + `ui-verify` 五门禁全绿(像素一致、池 4784/4864 B 不变) |
| 2026-09-09 | 用户密钥密码页落地(首个表单/操作页,§4.7 / `内容页架构.md` §4.1):框架描述符新增 title_y/hide_deco/hide_footer/key_intercept + 新 API `ui_content_page_invalidate`;新模块 `ui_page_password`(3 数字框反显光标 + arm API);菜单受保护叶子路由(arm→开密码页,确认一致后回调**先关页再续做进入动作**);字体 97→102 汉字(198 字形);双端注册;回归扩至 7 页 + 往返水位,五门禁全绿 |
| 2026-09-10 | 网口设置内容页(§4.7 / `内容页架构.md`):新增 `Drivers/BSP/NETCFG`(Flash 持久化 + CRC-16,工厂默认 192.168.1.100/24)+ 页面插件 `ui_page_net_cfg`(5 行动态值 + 可编辑:↑↓调字节/←→移光标/ENTER 确认,保存行 ENTER → `net_cfg_save` 下次上电生效);框架新增钩子 `init_cb` + API `ui_content_page_selected_row`;`net_init` 增 `mac` 参数、netTask 等 `SYS_EVT_STORAGE_READY` → `net_cfg_load` → `net_init(ip,mask,gw,mac)` → 回写实际 MAC 影子;字体 102→111 字符(208 字形,修复低码位符号丢失);双端注册;回归扩至 11 页,五门禁全绿 |
| 2026-09-14 | 运行记录/自检记录内容页 + 待机显示模块:新增 `App/log/sys_log`(定长 64B 文本记录引擎,MCU 落 NAND `/log/run.log`、`/log/selftest.log`,FTP 可直下)+ 页面插件 `ui_page_runlog` / `ui_page_selftest`(动态列表:count_cb + refresh,每条 2 行时间戳/描述)+ `ui_standby`(空闲超时自动切待机屏,仅确认键回菜单);密码页错误提示"密码输入错误"+清空重输;字体 111→122 字(217 字形);详见 `docs/运行与自检记录设计说明.md` |
Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

+1
View File
@@ -477,3 +477,4 @@ extern const content_page_def_t ui_page_device_info_desc; /* 描述符(Flash
| 2026-09-10 | 网口设置内容页:新增 `Drivers/BSP/NETCFG/`(Flash 持久化 magic 'NCFG'+CRC-16/MODBUS,工厂默认 192.168.1.100/24、gw .1、MAC 全零=芯片内置)+ 页面插件 `ui_page_net_cfg`(5 行动态值 + 可编辑:↑↓调字节 0..255 环绕 / ←→移光标 / ENTER 确认,保存行 ENTER → `net_cfg_save` 下次上电生效);框架新增描述符钩子 `init_cb`(开页重置运行态)+ API `ui_content_page_selected_row`;`net_init` 增 `mac` 参数、netTask 改为等 `SYS_EVT_STORAGE_READY` → `net_cfg_load` → `net_init(ip,mask,gw,mac)` → 回写实际 MAC 影子;模拟器 main 调 `net_cfg_load` 使参考图显示工厂默认值;字体修复:lv_font_conv 丢弃低码位符号(U+FF0C 全角逗号 < 0x4E2D 范围起点 → 页脚逗号长期空白)改由 `merge_extra_symbols()` 补回 + 原本缺字(存/密/户/本/用/钥)→ 111 字 / 208 字形;回归扩至 11 页(网口设置 4 态:可编辑/只读/编辑光标反显/保存 OK),五门禁全绿 |
| 2026-09-10 | 修复网口设置页编辑态渲染 bug:光标段原传整串 `value + start`,而 `lv_draw_label` 按 `coords`(光标盒,仅段宽)折行 → 逐行向盒下方绘制、溢出到其它行与空白区("下方弹出一串字符")。改为只传**段文本**(IP/掩码/网关 3 字符、MAC 2 字符);§3.5 补"`lv_draw_label` 折行陷阱"说明 |
| 2026-09-10 | 网口设置页 MAC 值改常识格式 **`A4:94:B6:12:34:56`**(冒号分隔,17 字符 = 102px);布局调整 名 x=6→4、值 x=60→54(MAC ink 止于 155,仍在白带 2..157 内);编辑光标段偏移 MAC 2i→3i |
| 2026-09-14 | 运行记录/自检记录动态列表页(`ui_page_runlog` / `ui_page_selftest`):描述符新增 `count_cb` + `refresh`(开页/翻页时重读条数与内容,每条 2 行 = 时间戳/描述),数据源为 `App/log/sys_log` 引擎的 `/log/run.log`、`/log/selftest.log`;**框架不变量 selectable ⇒ band_h>0**——band_h=0 时选中行恒黑字无白带 → 黑底不可见(首版两页均踩中,改 band_h=13 对齐 AC/DC 约定);新坑:本 LVGL 版本 `lv_text_get_width` length≠0 时无 NUL 检查,传 -1 越串尾死循环,必须传 strlen(密码页错误提示首版触发,已修并全库排查) |
+1 -1
View File
@@ -792,7 +792,7 @@ ESC/LEFT : d>0 → menu_back() (根框 d=0 不响
1. 本 LVGL v9.3 的 `lv_text_get_width(txt, length, font, letter_space)` **length=0 时返回 0**(不是"整串");必须传 `strlen`——否则框宽计算全 0(根框退化 8 px)。
2. 无头模式**不得**调 `lv_timer_handler()` / `lv_refr_now()`:I1 + `LV_DISPLAY_RENDER_MODE_FULL` 全屏刷新会进渲染死循环(截图因此走 `lv_snapshot`);`--keys` 注键是同步处理,无需驱动帧。
3. 选中行**保留 ▶**(随反色变黑):参考图 02/04 原图即"选中行箭头仍在、仅随反色变黑",勿实现成"选中隐箭头"(§4.4)。
4. 字体缺字形 = 方框:菜单树新增/改名的汉字必须同步加入字符集 `ui_cn_chars.txt`(当前 78 唯一字)并重生成字体(重跑 `extract_cn.py` + `make_font.py`),否则新字显示为方框(流程见 `UI_中文字体生成与配置.md`)。
4. 字体缺字形 = 方框:菜单树新增/改名的汉字必须同步加入字符集 `ui_cn_chars.txt`(当前 122 唯一字)并重生成字体(重跑 `extract_cn.py` + `make_font.py`),否则新字显示为方框(流程见 `UI_中文字体生成与配置.md`)。
5. **参考图保真度**:参考图为手工 mockup,4 个样本(子框顶位置)不满足单一公式;实现采用自洽规则(顶对齐/底对齐上移),实测与参考图偏差:顶位置 2/4/6/1 px(三遥/装置维护/通讯参数/记录查询),框宽 2/8/0/2 px(4字+箭头/4字/3字/2字+箭头)。两端极例(三遥、记录查询)精确吻合;统一规则优于逐屏特调,偏差范围内不影响识读与操作。
6. 装饰"浅 V"两腿线宽必须 3(水平线 2):1bpp 下细斜线边缘被阈值吃掉,腿不粗于水平线即消失;且依赖 `LV_DRAW_SW_COMPLEX=1`(见 `UI_设计架构.md` §1.4 约束 7)。
+277
View File
@@ -0,0 +1,277 @@
# 运行/自检记录与待机显示设计说明
> **版本**:v1.1(已实施)
> **日期**:2026-09-14
> **状态**:S1–S9 全部完成——双端构建 0/0、`--logtest` PASS、ui-verify 五门禁全绿;真机 @test 待上电回归(§8.2)
> **关联文档**:`项目架构总览.md` · `STM32F4-Base存储架构说明.md` · `系统墙钟时间维护说明.md` · `UI 设计/内容页架构.md`(如存在)· `docs/UI 设计/mockups/`(待机显示页面.jpg / 运行记录页.jpg / 自检记录页.jpg / 密码页面实际机器的示例图片.jpg)
## 1. 背景与需求
| # | 需求 | 说明 |
|---|------|------|
| R1 | 密码错误提示 | 用户密钥页输错密码时提示"密码输入错误",并允许重新输入(现状:仅清空重输、无提示) |
| R2 | 待机显示页 | 长时间无操作时默认显示的界面(参考 `mockups/待机显示页面.jpg`:6 路电流值 + 底部时钟);**只有按确认键才进入菜单** |
| R3 | 运行记录页 | 记录装置上电时间、重启时间(参考 `mockups/运行记录页.jpg`:标题 + 逐条"时间戳 + 事件" + 页脚分页) |
| R4 | 自检记录页 | 记录开机自检结果,有问题才记录(参考 `mockups/自检记录页.jpg`) |
| R5 | FTP 可读存储 | 记录以**纯文本文件**落盘,FTP(根目录 = FatFS 卷根)可直接下载读取 |
| R6 | 先调研后设计 | 网络调研存储方式选型 → 写入本设计文档 → 按文档实施 |
## 2. 现状分析
### 2.1 UI 框架(`Drivers/BSP/UI/`,MCU/模拟器共用)
- **内容页 = 描述符驱动**:`content_page_def_t`(标题/几何/行数据源/4 钩子),框架 `ui_content_page.c` 统一建屏、切屏、焦点、按键分发与绘制;页面模块零 LVGL 对象。
- **分页已内建**:`selectable=1` + `rows_per_page`(↑↓移选、窗口跟随)+ 页脚"第 X 页,共 Y 页"自动计算;动态行经 `refresh(row, name, value)` 逐行取数(栈缓冲 **32B/列**),行数经 `count_cb()`。
- **菜单树已含目标叶子**:`ui_menu_tree.txt` "记录查询"下已有 **自检记录 = node 39、运行记录 = node 40**(当前为空白终点框,无内容页映射);无需改树。
- **密码页** `ui_page_password.c`:3 位数字框 + ↑↓←→/ENTER;输错仅清空重输(R1 缺口)。
- **按键流**:物理/远端键 → LVGL keypad → 默认输入组 → 焦点对象事件回调(菜单容器 `menu_key_cb` / 内容页容器 `cp_key_cb`)——空闲检测可在此统一挂钩。
- **字体**:子集位图字体 `ui_font_cn_12`,字符集 `ui_cn_chars.txt`(当前 122 汉字/符号 + ASCII)。
### 2.2 存储与时间
| 项 | 现状 |
|---|------|
| 卷 | GD5F2GQ5UE SPI NAND **256MB**(2048 块 × 64 页 × 2KB),dhara FTL(磨损均衡/坏块管理),FatFS FAT32,`FF_USE_LFN=0` → **文件名必须 8.3** |
| FTP | lftpd 根目录 = 卷根 `/`;现有文件 `/config.ini`、`/cfg/netcfg.bin`(均 8.3) |
| 并发 | `FF_FS_REENTRANT=1` + 系统锁(`ffsystem.c` osMutex):单次 `f_*` 调用受保护,**多调用序列(open→seek→write→close)需单写者纪律** |
| 墙钟 | `sys_clock_get_str(buf,20)` → `"YYYY-MM-DD HH:MM:SS"`;`sys_clock_get()` → Unix 秒(失败 = `UINT32_MAX`);SD2506 RTC + timeSyncTask TCP 校时 |
| 挂载 | defaultTask:`f_mount(forced=1)`,空卷自动 `f_mkfs` FAT32,成功后置 `SYS_EVT_STORAGE_READY` |
### 2.3 现有自检检查点(v1 埋点位置)
| 检查点 | 位置 | 失败表现 |
|---|------|---------|
| RTC 读取/墙钟建立 | `app_main_init()`:`sd2506_init()` + `sys_clock_init()`(FreeRTOS 启动前) | 时间戳无效,后续日志无意义时间 |
| NAND 初始化 | `app_main_init()`:`gd5f2gq5ue_init()` 返回非 `GD5F_OK` | 存储不可用 |
| ADC 初始化 | `app_main_init()`:`tpafe5160_init()` 超时 | 采样不可用 |
| FatFS 挂载/建卷 | defaultTask:`f_mount`/`f_mkfs` 失败(置位 STORAGE_READY 前) | FTP/配置/记录全部不可写 |
| 网络链路 | net_task `net_stack_bring_up()`:PHY 等待 ≤3s 后仍 DISCONNECT | 组网可用但无链路 |
### 2.4 RAM/LVGL 池预算(依据 `docs/内存管理/RAM使用分析与优化方案.md`)
- FreeRTOS 堆真实余量 ≈ **11~12KB**;LVGL 池 40KB 静态,菜单常驻占用 ≈5KB。
- 本设计新增 RAM:**全部 < 1KB**(§7),不新增任务、不动任务栈。
## 3. 网络调研摘要(R6)
| 主题 | 结论 | 来源 |
|---|------|------|
| 日志行格式与时间戳 | syslog(RFC 5424):一行一条记录;TIMESTAMP 采用 **ISO 8601/RFC 3339**(`YYYY-MM-DDTHH:MM:SS[.frac][Z]`,机器可读、字典序可排序);MSG 用 UTF-8。本设计取空格变体 `YYYY-MM-DD HH:MM:SS`(与既有 `sys_clock_get_str` 输出一致),另附 epoch 秒字段供程序化解析 | RFC 5424 §6.2.3/§6.4(datatracker.ietf.org/doc/html/rfc5424) |
| 写入模型 | Linux printk:消息先进 **RAM 环形缓冲**,由**专用线程**异步刷持久存储;热路径禁止同步 I/O。本设计对应"RAM 待发队列 + defaultTask 单写者落盘" | kernel.org/doc/html/latest/core-api/printk-basics.html |
| FatFs 追加语义 | `FA_OPEN_APPEND`(= FA_WRITE\|FA_SEEKEND):打开即定位 EOF,文件不存在则创建;配合 `f_lseek(fp, FILE_SIZE)` 可取文件大小。本设计每条记录一次 `f_open→f_write(64B)→f_close` | 工程内 `Lib/FatFs/ff.h:399`、`ff.c:3871`(与官方文档一致) |
| NAND 写入代价 | dhara FTL 已做磨损均衡;FAT32 下每次小追加约消耗 1 个簇(4KB)。本场景**写频率极低**(每上电 1~N 条、故障才写),簇开销与磨损可接受;以文件容量上限约束增长 | `docs/STM32F4-Base存储架构说明.md` §6.3 + GD5F 驱动参数 |
**选型结论**:纯文本、**定长 64B 记录、一行一条**(ISO 时间戳 + epoch + UTF-8 描述);追加写 + 容量上限截断;RAM 队列 + 单写者任务落盘。
不选变长行(需扫描计数/解析,掉电半行难界定);不选二进制格式(违背 R5 FTP 直读)。
## 4. 总体设计
### 4.1 模块与文件清单
| 文件 | 端 | 说明 |
|---|---|------|
| `App/log/sys_log.h` | 共享 | 公共 API + 记录格式常量 + 行拆分内联函数(无 OS 依赖) |
| `App/log/sys_log_mcu.c` | MCU | FatFS 后端:待发队列(启动前静态数组 + FreeRTOS 消息队列)、defaultTask 落盘、容量截断、上电/重启分类 |
| `sim/sim_syslog.c` | 模拟器 | 宿主文件后端(CWD 下 `sim_run.log` / `sim_selftest.log`),直接追加,`time()` 时间戳 |
| `Drivers/BSP/UI/ui_standby.h/.c` | 共享 | 空闲检测 + 待机屏(屏幕覆盖层:保存/还原前屏与焦点) |
| `Drivers/BSP/UI/ui_page_runlog.h/.c` | 共享 | 运行记录内容页描述符(动态行,数据源 = sys_log API) |
| `Drivers/BSP/UI/ui_page_selftest.h/.c` | 共享 | 自检记录内容页描述符(同上) |
| `App/app_main.c` | MCU | 埋点:RTC/NAND/ADC 失败 → `syslog_post(SYSLOG_SELFTEST, …)` |
| `Src/freertos.c`(defaultTask USER CODE) | MCU | 挂载失败埋点 + **上电/重启分类记录** + 队列排空落盘循环 |
| `App/task/net_task.c` | MCU | 埋点:链路超时 → `syslog_post(SYSLOG_SELFTEST, "通讯异常")` |
| `Drivers/BSP/UI/ui_page_password.c` | 共享 | R1:错误提示 + 重输 |
| `Drivers/BSP/UI/ui_content_page.h/.c`、`ui_menu.c` | 共享 | 新增 2 个内容页 ID/描述符表项/node 39·40 映射;键事件挂钩空闲通知 |
| `ui_cn_chars.txt` + `tools/font_gen/` | — | 补 10 字重生成字体(§6) |
| `MDK-ARM/*.uvprojx`、`simulator/CMakeLists.txt` | — | 新文件双端注册(new-source-file skill) |
| `simulator/main.c` | 模拟器 | `--logtest` / `--standby` / `--idle <s>` 无头自检与测试钩子 |
### 4.2 日志记录格式(定长 64B,一行一条)
```
偏移 长度 内容
[0..18] 19 "YYYY-MM-DD HH:MM:SS"(ISO 8601 空格变体;墙钟无效时 = "1970-01-01 08:00:00")
[19] 1 ' '
[20..29] 10 epoch 秒 ASCII("%010u",机器解析/上电重启分类用;墙钟无效 = "0000000000")
[30] 1 ' '
[31..58] 28 描述 UTF-8(≤28B = 9 个汉字),不足补 '\0'
[59..63] 5 '\0' 填充
```
示例行:`2026-09-14 16:48:34 1789465714 装置上电记录`(后随 NUL 填充至 64B)。
**定长收益**:条数 = `filesize / 64`(O(1),免扫描);第 k 条偏移 = `k*64`(随机读,UI 翻页零解析成本);掉电半行被 `floor(size/64)` 自然排除。
**epoch 10 位约束**:2026 年 Unix 秒已达 10 位(2001-09-23 起),字段按 `%010u` 定宽;uint32 上限 4294967295(2106 年)内不溢出。
**描述 ≤28B 约束**:受框架动态行栈缓冲 `CP_ROW_BUF_LEN=32`(含 NUL)限制,不改框架。
### 4.3 文件与容量
| 文件 | 内容 | 上限 | 满时策略 |
|---|------|------|---------|
| `/log/run.log` | 上电/重启记录 | **1024 条(64KB)** | 删除重建(整卷清空,串口告警);DTU 上电频率低,容量充裕 |
| `/log/selftest.log` | 自检故障记录 | **512 条(32KB)** | 同上 |
- 目录 `/log/` 首次落盘前 `f_mkdir`(已存在则忽略);文件名均 8.3 合规。
- FTP 视角:卷根下多一个 `LOG` 目录,两个 `.log` 文本文件可直接下载(R5)。
- 记录**只增不改不删**(除满时重建与未来"清除记录"功能),保证 FTP 端所见即历史。
### 4.4 写路径(单写者,调研结论落地)
```
任意上下文 syslog_post(id, desc)
├─ FreeRTOS 未运行(app_main_init 期)→ 静态待发数组(8 槽,满则丢弃+计数)
└─ 调度器已运行 → osMessageQueueSend(8 槽,满则丢弃+计数;日志不得阻塞业务)
defaultTask(唯一写者,挂载成功后):
1) f_mkdir("/log")(幂等)
2) 排空启动前数组 + 队列 → 逐条落盘(f_open FA_OPEN_APPEND → f_write(64B) → f_close)
3) 写"上电/重启"分类记录(§5.3,基于 run.log 末条 epoch)
4) 主循环:osMessageQueueGet(阻塞, 10s 超时) → 落盘;超时空转继续 LED 闪烁
```
- 读路径与写路径并发安全:UI 页在 uiTask 直接 `f_open(FA_READ)` 按偏移读记录(只读既有内容,写者只在 EOF 追加);FatFS 系统锁保护单次调用。
- **存储未就绪/挂载失败**:待发条目滞留 RAM(下次上电丢失),串口打印告警——存储故障时本就无法持久化,属预期行为。
### 4.5 读路径(UI / 模拟器)
```c
uint32_t syslog_count(syslog_id_t id); /* = filesize/64 */
int syslog_get_record(syslog_id_t id, uint32_t idx, char *ts, size_t ts_len,
char *desc, size_t desc_len); /* 越界返回 -1 */
```
- MCU:`f_open(FA_READ) → f_lseek(idx*64) → f_read(64B) → f_close`,内联拆分函数切 ts/desc。
- 模拟器:宿主文件同语义(UI 页双端零差异)。
- UI 页每帧仅读当前窗口 ≤8 条记录(每次重绘 8×64B SPI 读,用户翻页触发,频率极低)。
## 5. 功能设计
### 5.1 R1 密码错误提示(`ui_page_password.c`)
- ENTER 比对失败 → `s_err=1`:3 框清零、光标回首框,**数字框下方居中显示"密码输入错误"**(y≈104)。
- 错误态下按 ↑↓←→:**先清除提示**再正常执行该键(即"让用户重新输入");再按 ENTER = 重新比对。
- 比对成功路径不变(回调授权/关页)。布局常量与绘制沿用现有 culling 模式,零 LVGL 对象。
### 5.2 R2 待机显示页(`ui_standby.c/h`)
**进入条件**:UI 初始化完成后,**60s(默认,可配)内无任何按键** → 切待机屏。
- 空闲检测 = `lv_timer`(1s 周期,MCU/模拟器均经 `lv_timer_handler` 驱动,无 OS 依赖);
- 活动通知挂钩:`menu_key_cb`、`cp_key_cb` 每收到一个键调 `ui_standby_notify_activity()`(重置计时)。
**待机屏内容**(参考 mockup,全 ASCII 无需新字体字):
```
Ib = 0.00A ← 6 行电流值(v1 占位 0.00A,与 AC/DC 页现状一致;
Ic = 0.00A 待交流量标定管线落地后接 TPAFE5160 实时值)
Io = 0.00A
Ia2 = 0.00A
Ib2 = 0.00A
Ic2 = 0.00A
────────────────────── ← 分隔线(y≈132)
YYYY-MM-DD HH:MM:SS ← 底部实时时钟(每秒重绘,lv_timer 驱动 invalidate)
```
**键语义(用户定案)**:待机屏上**只有 ENTER(确认键)进入菜单**;其余按键忽略(同时重置空闲计时)。
- 实现为**屏幕覆盖层**:进入时保存当前活动屏 + 组内焦点对象,加载待机屏并聚焦其容器;ENTER 时还原前屏与焦点。
- 内容页打开期间超时 → 同样切待机(页面对象保留在底层,ENTER 返回后原样恢复,编辑态不丢)。
### 5.3 R3 运行记录(上电/重启分类)
defaultTask 挂载成功、排空待发队列**之后**写一条:
```
读 run.log 末条记录的 epoch 字段 E:
墙钟有效 且 (now - E) < 600s → "装置重启记录"(近期刚运行过 = 软复位/看门狗)
其余(无文件/首条/间隔≥10min/墙钟无效)→ "装置上电记录"
```
- 阈值 `SYSLOG_REBOOT_WINDOW_S = 600`:断电 <10min 会被误判为重启——无掉电检测硬件时该歧义不可消除,取工程折中并在此声明。
- 串口标记(供 @test):`runlog boot entry: 装置上电记录 / 装置重启记录`(`sys_log_mcu.c` `DBG_INFO`,与落盘描述串同源)。
### 5.4 R4 自检记录(v1 项表)
| 检查点 | 触发时机 | 描述串(落盘 + UI 显示同源) |
|---|------|------|
| RTC/墙钟建立失败 | app_main_init | `时钟芯片异常` |
| NAND 初始化失败 | app_main_init | `NAND异常` |
| ADC 初始化超时 | app_main_init | `ADC异常` |
| FatFS 挂载/建卷失败 | defaultTask(仅串口,存储不可写) | (无法落盘,串口 `[SELFTEST] fs: FAIL`) |
| 网络链路 3s 未建立 | net_task bring_up 后 | `通讯异常` |
- **只记故障**;全部通过不写记录(与 mockup"有问题进行记录"一致)。
- v1 不做"已复归/动作/标志字"(mockup 中为保护继电器专有语义)——列入开放问题 O2。
- UI 页空态:`count_cb` 返回 0 时以单条伪行 `无记录` 呈现(页脚"共 1 页",避免"共 0 页")。
## 6. UI 布局与字体
### 6.1 新增字符集(11 字)→ 重生成 `ui_font_cn_12.c`
| 用途 | 新字 |
|---|------|
| 密码错误提示"**密码输入错误**" | 输、入、错、误 |
| 运行记录"装置**上电**记录 / 装置**重**启记录" | 上、电、重 |
| 自检故障名"时**钟芯片异常**"等(异/常为公共后缀) | 芯、片、异、常 |
流程按 `font-regen` skill:改 `ui_cn_chars.txt` → extract_cn → make_font → diff。注意:**extract_cn 只扫 UI 源码**,故障描述串在非 UI 源码(sys_log_mcu.c/app_main.c/net_task.c)中,须手动补入字符集;其余文案(运行记录/自检记录/无记录/第 X 页共 Y 页)均已在字符集内;待机页全 ASCII。
### 6.2 页面几何(160×160,沿用框架常量体系)
| 页 | 参数 |
|---|------|
| 运行记录 / 自检记录 | `selectable=1, rows_per_page=8, top_y=24, pitch=14, band_h=13`(同 AC/DC 约定;**偏离 mockup**:实机照片无选中带,但框架 selectable 页选中行恒黑字、band_h=0 时黑字黑底不可见,故保留标准白带),`left_x=7`;**每条记录 = 2 行**:偶数行 = 时间戳(`YYYY-MM-DD HH:MM:SS`,19 ASCII ≈114px 可容)、奇数行 = 描述(≤9 汉字);↑↓按行移动(8 行/页 = 4 条/页),页脚自动分页 |
| 待机屏 | 6 数据行 y=28 起 pitch=18(label x≈24 / `=` x≈72 / 值 x≈96);分隔线 y≈132;时钟 y≈140 居中 |
| 密码页错误提示 | "密码输入错误" 居中,y≈104(数字框行 y=78..92 之下) |
## 7. 内存预算
| 项 | 大小 | 位置 |
|---|------|------|
| sys_log MCU:启动前待发数组 8×12B + 队列 8×12B + 状态 | ≈300B(静态)+ ≈150B(FreeRTOS 堆,消息队列) | .bss / heap_4 |
| ui_standby:待机屏 + 容器 LVGL 对象(常驻) | ≈300B | LVGL 池(40KB,当前占用 ≈5KB) |
| 记录页模块 | 0(描述符驱动,零 LVGL 对象;仅栈缓冲) | — |
| 字体 +10 字形 | ≈250B Flash | .rodata |
**结论:不新增任务、不动任务栈、FreeRTOS 堆增量 <200B,LVGL 池余量充足。**
## 8. 测试计划
### 8.1 模拟器(无头自动化)
| 用例 | 手段 |
|---|------|
| 日志引擎闭环 `--logtest` | post N 条 → count/get 回读断言 + 宿主文件字节级校验(64B 定长、ISO 时间戳、NUL 填充),0=PASS(仿 `--cfgtest`) |
| 待机屏渲染 `--standby` | 无头注键后直接调测试钩子强制进入待机 → 截图比对(布局/时钟行);窗口模式 `--idle <s>` 可人工验证真实超时路径 |
| 记录页像素回归 | `--keys` 导航至 node 39/40 → 截图 + 文本断言(时间戳行/描述行/页脚分页),纳入 ui-verify 门禁 |
| 密码错误提示 | `--keys` 输错密码序列 → 截图断言"密码输入错误"可见;再按方向键 → 提示消失 |
### 8.2 真机(@test 串口标记)
Required:`App init OK`、`Network Init`、`net init: OK`、**`runlog boot entry:`**(装置上电记录 / 装置重启记录)、**`UI standby: ready (idle=60s)`**;
Forbidden:`FAIL`/`ERROR`/`HardFault`。
人工目检:待机屏(静置 60s)、记录页翻页、FTP 下载 `/log/*.log` 文本可读性。
## 9. 风险与开放问题
| # | 项 | 策略 |
|---|----|------|
| O1 | 断电 <10min 的"上电/重启"歧义 | §5.3 阈值折中,文档声明;如需精确可后续加掉电标志(PVD) |
| O2 | 故障"已复归"/持续监测(mockup 语义) | v2:周期自检 + 活动故障状态机 + 恢复记录 |
| O3 | "清除记录"菜单项(node 27 #P,现为空框) | v2:UI 动作 = 删除/重建 log 文件(与满时策略同路径) |
| O4 | 待机页电流值接实时数据 | 待交流量标定管线(AC 页占位数据替换)落地后接入 `tpafe5160_get_buf()` |
| R-1 | FAT32 小追加簇开销 | §3 调研结论:写频率极低可接受;容量上限约束增长 |
| R-2 | UI 读与落盘并发 | 只读既有内容 + EOF 追加 + FatFS 系统锁,无互斥需求(§4.4) |
## 10. 实施步骤(按序执行,每步双端构建 0/0)
> ✅ **S1–S9 已于 2026-09-14 全部完成**:Keil `@build` 0 错 0 警 + 模拟器 0 警告;S8 = `--logtest` PASS(期间修复 ts 19 字符 / epoch 10 位两处格式 bug)+ ui-verify 五门禁全绿(池基线 5440/5520B、pwc 参考更新)+ 记录页/待机屏/密码错误提示截图目检通过;S9 = 文档同步。
| # | 步骤 | 产物 |
|---|------|------|
| S1 | 字体补字 + 重生成 | `ui_cn_chars.txt`、`ui_font_cn_12.c` |
| S2 | 日志引擎:`sys_log.h`(共享 API/格式)+ `sys_log_mcu.c` + `sim/sim_syslog.c` | 双端可 post/count/get |
| S3 | 自检埋点 + defaultTask 落盘循环 + 上电/重启分类 | app_main/freertos/net_task 接线 |
| S4 | 密码错误提示 | ui_page_password.c |
| S5 | 待机屏 `ui_standby`(空闲检测 + 覆盖层)+ 键挂钩 | ui_menu/ui_content_page 各一行通知 |
| S6 | 记录页 ×2 + 内容页框架接线(枚举/描述符表/node 映射) | ui_page_runlog/selftest、ui_content_page.h/.c、ui_menu.c |
| S7 | 双端注册新文件(uvprojx + CMakeLists)+ 构建 0/0 | new-source-file skill |
| S8 | 模拟器无头回归(--logtest/--standby/截图断言)+ 真机 @test | 测试通过记录 |
| S9 | 文档同步(项目架构总览、AGENTS.md、本文档状态) | docs |
+14 -4
View File
@@ -52,11 +52,11 @@
| 任务 | 栈 (字节) | 优先级 | 职责 | 入口 |
|---|---|---|---|---|
| `defaultTask` | 2048 | Normal | FatFS 挂载(无卷 `f_mkfs`)→ 置 `STORAGE_READY` → LED 闪烁 | `StartDefaultTask` |
| `defaultTask` | 2048 | Normal | FatFS 挂载(无卷 `f_mkfs`)→ `syslog_mcu_mounted` + 日志落盘(唯一写者,LED 循环内 `syslog_drain`)→ 置 `STORAGE_READY` → LED 闪烁 | `StartDefaultTask` |
| `netTask` | 4096 | Normal | 等存储 → `net_stack_bring_up()` → 置 `NET_READY` → `net_poll` 轮询 | `StartNetTask` |
| `ftpTask` | 4096 | Normal | 等网络+存储 → `lftpd_start("/", 21)` | `StartFtpTask` |
| `testTask` | 4096 | BelowNormal | 测试套件入口(`test/test_config.h` 选择;产品构建空转) | `start_test_task` |
| `uiTask` | 16384 | BelowNormal | 等网络 → `ui_port_init`/`ui_menu_init`/`ui_net_sink` → 10ms 刷新 | `ui_task_func` |
| `uiTask` | 16384 | BelowNormal | 等网络 → `ui_port_init`/`ui_menu_init`/`ui_standby_init`(空闲 60s 切待机屏)/`ui_net_sink` → 10ms 刷新 | `ui_task_func` |
| `timeSyncTask` | 2048 | Normal | 等网络 → TCP :8888 收 PC 推送时间 → 写 RTC | `time_sync_task` |
> `idle`(128 words)/ `timer`(256 words)由 FreeRTOS 内核创建。所有任务栈从 48KB heap_4 堆分配。
@@ -133,11 +133,13 @@ flowchart LR
subgraph USERS["使用方"]
NC["net_cfg<br/>/config.ini(INI [network] 段)"]
SLG["sys_log<br/>/log/run.log · selftest.log"]
LF["lftpd<br/>FTP 文件读写"]
ST["测试套件<br/>STORAGE/GD5F"]
end
NC --> FS
SLG --> FS
LF --> FS
ST --> FS
FS --> DIO --> FTL --> NAND --> CHIP
@@ -148,6 +150,7 @@ flowchart LR
```
- 掉电持久化:`net_cfg` 配置(通用配置文件 `/config.ini`,INI `[network]` 段,cfg_file 引擎),保存后下次上电生效、或立即生效(网络重建)、外部变更 4s 沉降后自动重载;旧版 NCFG 二进制(`/cfg/netcfg.bin`)首启自动迁移。
- 日志记录:`sys_log` 引擎写定长 64B 文本记录到 `/log/run.log`(运行记录)与 `/log/selftest.log`(自检记录),defaultTask 唯一写者,FTP 可直下(详见 `docs/运行与自检记录设计说明.md`)。
- NAND 坏块表(BBT)持久化在保留块池,不交给 dhara 管理。
---
@@ -195,6 +198,8 @@ flowchart TB
P3["ui_page_dc"]
P4["ui_page_password"]
P5["ui_page_net_cfg"]
P6["ui_page_runlog<br/>运行记录(动态列表)"]
P7["ui_page_selftest<br/>自检记录(动态列表)"]
end
subgraph PORT["显示端口"]
UP["ui_port.c<br/>160x160 1bpp 缓冲"]
@@ -204,17 +209,20 @@ flowchart TB
UM --> MT
UM --> MV
UM --> CP
UM --> STBY["ui_standby.c<br/>待机屏(空闲超时切入)"]
MV --> DECO
CP --> P1 & P2 & P3 & P4 & P5
CP --> P1 & P2 & P3 & P4 & P5 & P6 & P7
MV --> UP
SINK --> UP
P5 -.->|"net_cfg RAM 副本"| CFG["net_cfg"]
P4 -.->|"ui_pwd_def.h 编译期密码"| PWD["查看 000 / 修改 123"]
P6 & P7 -.->|"/log/run.log · selftest.log"| SLG["App/log/sys_log<br/>定长 64B 记录引擎(defaultTask 落盘)"]
```
- 内容页为**独立全屏屏**,由框架一次性自绘(**零 LVGL 子对象**),ESC 返回菜单屏。
- 菜单受保护叶子(`ui_menu_tree.txt` 行尾 ` #P`):先弹密码页,授权后 `menu_password_ok` **先关页再续进入动作**。
- 字体 `ui_font_cn_12.c` 由 `tools/font_gen/` 生成(宋体 12px 1bpp 子集,111 字/208 字形)。
- 待机屏:空闲超时(默认 60s)自动切入,仅确认键回菜单;数据源同实时量页。
- 字体 `ui_font_cn_12.c` 由 `tools/font_gen/` 生成(宋体 12px 1bpp 子集,122 字/217 字形)。
---
@@ -231,6 +239,7 @@ STM32F4-Base/
│ ├── sys_events.h 就绪屏障 bit 定义
│ ├── app_cfg.h 功能编译开关(APP_NET/ADC/RS485_ENABLE)
│ ├── task/ net_task / ui_task / adc_task* / rs485_task*
│ ├── log/ sys_log 记录引擎(定长 64B;MCU 后端落盘 /log/*.log)
│ └── util/ crc(CRC-16/8)、ringbuf、sys_clock(墙钟)
├── Drivers/BSP/ 纯手工驱动/服务(不受 CubeMX 保护)
│ ├── CH395F/ 以太网 MAC+PHY
@@ -306,3 +315,4 @@ flowchart LR
| 2026-09-10 | §2.2 就绪屏障图由 Mermaid 改为**手绘 SVG**(`tools/arch_diagram/gen_barrier_svg.py` → `就绪屏障.svg`):三栏(生产者 / 事件组 / 消费者)+ 水平对齐 + All 汇合点,消除原图 9 条边的交叉;PNG 预览本地渲染、不入库 |
| 2026-09-13 | `sys_clock` 由 ③基础服务层改归 **④中间件层**(通用平台时间服务,日志/文件/对时共用;文件移入 `App/util/` 与 crc/ringbuf 同目录,include 路径不变):`FatFs` → `sys_clock` 成为**层内引用**,工程反向依赖消除;同日新增的 §1.2 "反向依赖例外" 条目撤销 |
| 2026-09-14 | 通用配置文件(P3):网口配置存储由 `/cfg/netcfg.bin`(NCFG 二进制)迁移至 `/config.ini`(INI `[network] 段`,`App/cfg_file` 引擎 + `cfg_file_store` 双端后端);`net_cfg` 对外 API 不变;旧档首启自动迁移;`cfg_file` 列为 MCU/模拟器共享源码 |
| 2026-09-14 | 运行/自检记录:新增 `App/log/sys_log`(定长 64B 文本记录引擎,MCU 后端 `/log/run.log`·`selftest.log`,defaultTask 唯一写者)+ UI 页面插件 `ui_page_runlog`/`ui_page_selftest`(动态列表)+ `ui_standby` 待机屏(空闲超时切入);密码页错误提示;字体 111→122 字(217 字形);详见 `docs/运行与自检记录设计说明.md` |