# 0.6a 实体 UI 清理：软件验证与刷写交接

本轮状态：**SOFTWARE PASS / PHYSICAL PASS**（2026-10-05）。
Battery、Monitor、Settings Back 与 UTC+8 已在 Hold A 修复固件 `48d4719`
复测通过，用户对两组完整实体 Gate 回复“没问题”。详见
[Hold A 收口记录](hold-a-back-closeout-0.6a.md)。
2026-10-05 用户已确认原 Watch AMOLED、时间、字体与风格的 Boot Gate PASS。
仅修改 BAT、Settings Back、Monitor；未重新设计 Watch。

## 1. 基线与隔离

Boot Gate 源版本：`a4bf8284125e39654d8a88bcc7c5b5c2e4424a07`。
依据 Mac 的 `.cache/wearable-shell-0.6a-cjk-physical/boot-gate-report.json`，
并核对 Lighthouse 主工作树的 HEAD 与 clean 状态。
修复分支：`codex/physical-ui-cleanup-0.6a`。

- Mac：`/Users/lili/workspace/esp32-dev/.cache/physical-ui-cleanup-0.6a`
- Lighthouse：`/home/lighthouse/esp32-dev/.cache/physical-ui-cleanup-0.6a`
- 原 Mac 主工作目录仍在 Speech 分支；未跟踪 Wi-Fi 配置保留。
- main、生产 Gateway 与原 Boot Gate artifact 未改动。
- 新 artifact 的源 commit、SHA-256 与软件状态见同目录 `candidate.json`。

## 2. BAT 根因与官方对照

旧实体串口在 PMIC begin 成功后、IOE 初始化后立即开始输出
`M5PM1_ADC: Not initialized`。实际 Boot Gate ELF 的 DWARF 提供直接软件证据：

| 类型 | board.cpp 大小 | vendor 驱动大小 |
|---|---:|---:|
| M5PM1 | 260 | 268 |
| M5IOE1 | 428 | 436 |

原因是 vendor compat header 使用 `__has_include(M5GFX.h)` 决定是否包含
`i2c_bus` 成员。板级可见 M5GFX，vendor 驱动不可见，造成 C++ ABI 不一致。
旧 ELF 中 IOE 地址 `0x3fca485c`，分配 428 字节；PMIC 紧邻在 `0x3fca4a08`。
驱动的 IOE `_aw8737aPulseNum` 从偏移 428 开始写四字节，覆盖 PMIC 的
`_addr`、`_initialized` 等开头字段。`_snapshotAw8737a()` 在 IOE begin 中
写这个成员。该枚举的高字节为零，PMIC 初始化标志因此被清除。
这解释了返回 NOT_INIT，而非仅仅 VBAT 数值越界。硬件电压仍须复测。

修复在项目 CMake 中给 M5PM1/M5IOE1 驱动加入 M5GFX 私有编译依赖，
统一 compat header 的可见性，不修改或升级锁定的 vendor 源码。
新 ELF 验证：PMIC 两侧均 260 字节、45 个成员偏移一致；IOE 两侧均 428
字节、34 个成员偏移一致；实际符号分配大小一致且不重叠。
`vendor_i2c_abi_test.py` 已加入 build.sh，防止成功链接掩盖同类错误。

已阅读 [M5Stack 官方 StopWatch PMIC](https://github.com/m5stack/M5StopWatch-UserDemo/blob/main/main/hal/hal_pmic.cpp)。
原 core power 配置已包含 CHG_EN 位，但缺少明确 `setChargeEnable(true)` 与
官方的 PMG3 CHG_PROG、PMG2 CHG_STAT 配置。本轮补齐：

- PMG3 = `M5PM1_GPIO_NUM_3`，output low / no pull / push-pull。
- PMG2 = `M5PM1_GPIO_NUM_2`，GPIO function / input / no pull。
- 保留 begin、两次 sleep=0、WDT=0、LDO hold、现有电源轨及 reset-disable。
- 每个新增初始化调用记录返回码。

## 3. Battery 模型与计算

纯函数位于 `device_status.h`，host 测试覆盖边界、滤波和错误保留。
模型记录 valid、raw/filtered mV、percent、external_power、charging、failures、last_error。

- 有效电压沿用 2000..5000 mV。
- 第一次有效值直接使用；之后 `(old*7 + new + 4)/8`，与官方四舍五入一致。
- ≤3300 mV 为 0%，≥4200 mV 为 100%；中间 `(mV-3300)*100/900`。
- 这是电压估算 SOC，不是精确 fuel gauge。
- 读取失败或 OK/invalid 都累计 failures，但 last_error 分别保留 vendor 负返回码
  与 `InvalidVoltage=1`。保留最近有效的滤波值/百分比；冷启动从未有效才显示 --。
- Watch：`BAT 73%`。System：`Battery 73%  3.92V`；宽度不足时分成两行。
- VIN 仅用于官方 >4000 mV 外接电源判定，不代替 VBAT；CHG_STAT=0 表示充电。
  两者只在低频诊断时更新，尚未宣称实体可靠性，不增加 Charging UI。
- 两个 vendor 对象完成初始化后做一次启动 probe；之后最多每 60 秒一条 `[POWER]`。
  包含 VBAT result/mV、filtered mV、percent、VIN/VREF result/mV、power source、
  CHG_STAT result/value、valid、failures、last_error。禁止逐秒刷错误日志。
- 后台每三秒读取 VBAT，录音期间跳过；显示仍只读取 cache。

## 4. Settings 最终状态机

短 A NEXT；短 B SELECT/APPLY；长 A BACK ONE LEVEL；长 B CLOSE。
Settings 内长 A 不会启动 Voice；Approval 与 Voice 原有优先级保持。

| 当前页 | 长 A |
|---|---|
| Root | 关闭 Settings，回原 App |
| Agent / Device / Display / System | Root |
| Edit | 撤销草稿，回 editor_parent；Brightness 恢复已应用值，不保存 |
| Effort | Model |
| Model / MODEL UPDATED / UPDATE FAILED | Settings Agent；清除 request identity |
| Speech Mode | Speech，撤销 mode 草稿 |
| Speech / Speech ROOT | Settings Agent |
| LOADING / APPLYING / SAVING SPEECH | 暂时禁止，显示忙状态；完成后可返回 |

B Apply 继续应用、保存并回父级；保存失败保留原有 APPLIED / NOT SAVED 语义。
结果页不再一秒后整体退出。Speech 的 back() 是明确状态转换，不模拟 click。
Footer 显示短按动作以及 `Hold A Back   Hold B Close`；保存反馈另占一行。

## 5. Monitor 与 NET

主环为 `Watch ↔ Agent ↔ Monitor ↔ Help`，正向/反向都立即响应。
UiState 不再有 monitor_page。进入 Monitor、Home=Monitor 均固定 Overview。
现有 CPU/Memory/Disk/Network 详情 renderer 留存，但 A/B 无入口。

Overview 四项同一级：CPU、MEM、DISK、NET。NET RX/TX 始终同一行，
采用与另外三行相同的 AgentBodyLarge 角色（Compact 18 / Normal 24 / Large 28 px）。
例：`RX 10KB/s TX 7KB/s`；宽度不足时先降低精度、压缩单位，如
`RX 123M/s TX 99M/s`，进一步必要时去掉前缀空格。不换行、不缩成 Meta、
不裁切。异常极端数值有同字号、界限明确的 HIGH fallback。
Footer 为 MONITOR / AGENT ONLINE，移除 1/5。
Read Screen 一次读 CPU/MEM/DISK 与 RX/TX。README、现状文档和 host 图已更新。

## 6. 软件验证

- `./scripts/test-server.sh`：exit 0。全部 firmware host harness、4 项 host 布局测试、
  18 项 Node 测试、141 项 Python 测试以及 HTTP/WSS、Admin、Pi DONE、Voice
  authenticated TLS/FAKE STT smoke 通过。
- `./scripts/test-network-resilience.sh`：exit 0，87 项 Python、18 项 Node 及
  production 网络/Voice/Approval 按键回归通过。
- Battery host：3300/4200/3750、上下界、7:1 rounded filter、invalid、NOT_INIT、
  transient error 与最近有效值保留通过。
- Settings production arbiter：全部父级、brightness 取消/恢复、成功 Apply/保存、
  保存失败、Model/Effort/Speech Back、请求忙时禁止 Back、Voice/Approval 优先级通过。
- Monitor：四页正反环、Home Monitor、单行 NET 同字号、多种速率及 round-safe；
  System 电池信息在全部字号/主题下通过。
- RTC BCD、offline advance/reconnect、Agent UTF-8/final_text、CJK generator 与
  linked CjkSc24/28 GB2312 一级 3755/3755 均通过。
- `./scripts/build.sh m5stack-stopwatch`：exit 0；实际 ELF ABI 测试通过。
- 日志保存在 Mac 工作树 `.cache/validation/`。上述结果是软件证据，不是新实体 PASS。

## 7. Mac 安全刷写

保留当前 Settings/NVS，使用 application-only，offset `0x10000`。
`update-flash.sh` 仅支持 main；它不适用于此隔离分支。
先关闭现有串口 monitor，让刷写程序独占端口；当前可见端口为 `/dev/cu.usbmodem1101`。
候选 partition-table 与 Boot Gate artifact 已做字节比较；app 大小低于 4 MiB 分区。

```bash
cd /Users/lili/workspace/esp32-dev/.cache/physical-ui-cleanup-0.6a
/Users/lili/workspace/esp32-dev/tools/mac/device.sh verify "$PWD/dist/m5stack-stopwatch"
/Users/lili/workspace/esp32-dev/tools/mac/.venv/bin/python -m esptool \
  --chip esp32s3 --port /dev/cu.usbmodem1101 --baud 460800 \
  write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB \
  0x10000 "$PWD/dist/m5stack-stopwatch/firmware.bin"
ESPPORT=/dev/cu.usbmodem1101 /Users/lili/workspace/esp32-dev/tools/mac/device.sh monitor
```

端口重新枚举时更新命令中的端口。刷写需 esptool hash verification 成功。
monitor 可能晚于启动日志打开，若未捕获 POWER 启动 probe，等待约 60 秒的诊断即可。
如需还原，Boot Gate app 位于原 Mac 的
`.cache/wearable-shell-0.6a-cjk-physical/artifacts/m5stack-stopwatch/firmware.bin`，
同样只刷 `0x10000`。

## 8. 只做三组实体复测

**A — Battery**：启动最多约 5 秒，Watch 应显示 `BAT xx%`。记录 `[POWER]` 的
vbat_result/raw mV/filtered mV/percent、VIN、power source。若仍 BAT --，立即停止
后续验收，保留日志，按实际返回码定位，不猜测、不用 VIN 或硬编码代替。

**B — Navigation**：A 走 Watch→Agent→Monitor→Help→Watch，B 反向。
不得经过四个 metric detail。Monitor 一屏显示 CPU/MEM/DISK/NET RX/TX，
NET 字号与其他三项同级，footer 无 1/5。

**C — Settings Back**：Device→Brightness，修改预览后 Hold A，应回 Device、
恢复亮度且没有保存。再修改、B Apply，应保存并回父级。
验证 Model→Hold A 回 Agent、Effort→Hold A 回 Model、Speech Mode→Hold A 回 Speech；
继续 Hold A 回 Agent。任意稳定 Settings 页 Hold B 整体退出。

只有用户真实看到有效 BAT 与这些行为正常，才能分别更新对应 PHYSICAL PASS。
