PRE-M8-T03: finalize WarmteLink hardware validation
frontend / frontend (push) Successful in 30s
pytest / test (push) Successful in 2m23s

This commit is contained in:
2026-08-22 19:29:54 +02:00
parent 2992bbb0ef
commit 8dbb59a3b7
4 changed files with 109 additions and 51 deletions
+65 -26
View File
@@ -1,8 +1,9 @@
# Pre-M8 — WarmteLink P1 真机概念验证
> **状态:进行中。** 2026-08-22 已完成临时脚本真机 bring-up,并确认两个可用累计量;
> 仓库内的正式 probe、解析测试和可重复验收流程尚未实现。完成下方 PRE-M8-T01T03
> 后,才把 Pre-M8 标记为完成并解除 M8 Planning 的入口限制。
> **状态:已完成。** 2026-08-22 已完成临时脚本真机 bring-up;随后仓库内正式 probe 以
> `115200 7N1` 连续运行 10 分钟,形成脱敏的可重复验收证据;最终人工走查开启供暖后又观察到
> 区域供暖累计量从 `0.017 GJ` 增至 `0.018 GJ`,并与物理表一致。Pre-M8 现已解除
> **M8 Planning** 的入口限制;M8 的架构和实现范围仍未锁定。
## 1. 目的
@@ -70,7 +71,8 @@ WarmteLink P1 → USB serial → 原始 telegram → 完整性状态 → 字段
### 3.3 Framing 与 CRC 异常
- 连续采集的 telegram 周期约为 10 秒;一次 8 帧盘点中每帧均为 256 字节。
- 临时 bring-up 的一次 8 帧盘点中每帧均为 256 字节;正式 10 分钟采样显示长度并不固定,
详见 §3.6。
- 正文结构稳定,版本、时间戳、两个 M-Bus channel、单位和值均可重复解析。
- 实测头部为 `)TU)2NWA-MYRSKY`,偶见 `)TU{2NWA-MYRSKY`;没有标准要求的 `/` 起始符。
- 帧尾存在 `!`,但其后的字符并非每帧都稳定为四位十六进制;即使恰好是四位,也无法从
@@ -111,6 +113,42 @@ M8 若要持久化这些读数,必须在 Planning 中单独锁定异常帧的
- 当前没有瞬时流量、温度或热功率;M8 只承诺累计量采集与展示。
- 串口 framing 和 CRC 异常尚未消失,必须作为正式 ingestion 的显式质量状态处理。
### 3.6 正式 CLI 10 分钟复验(脱敏)
2026-08-22,仓库内 `python -m scripts.p1_probe` 在真实
`/dev/serial/by-id/<redacted>` 上以 `115200 7N1` 运行 `--duration 600 --show-changes`,原始
bytes 仅写入 `/tmp`。CLI 正常以 exit code 0 结束;期间没有 I/O error、未处理异常或断连。
- 读取到 60 个完整 frame、15,362 raw bytes;最后 2 bytes 是下一帧的不完整残片,60 个完整帧
合计 15,360 bytes。
- 设备 timestamp 从 `18:16:10``18:26:00`,严格每 10 秒递进。CLI 处理的 59 个相邻间隔中,
53 个为 10.0 s、1 个为 9.0 s、3 个为 0.0 s、2 个为 20.0 s,均值 9.81 s0/20 s 配对来自
serial chunk 的批量交付,不代表设备 cadence 改变。
- 完整 frame 长度分布为 237 bytes × 2、239 × 1、254 × 12、256 × 31、258 × 11、275 × 3
平均 256 bytes、范围 237–275。不能再把临时样本的「固定 256 bytes」视为帧格式契约;变长与
非标准 footer/分块边界一致。
- 60/60 的完整性均为 `unverifiable`,因为 60/60 缺少标准 `/` 起始符。footer 长度为 4 × 41、
6 × 16、23 × 3;仅 16/60 的 footer 恰为四位十六进制,但仍不能在缺失 `/` 时完成标准 CRC16
验证。**没有任何 frame 被报告为 CRC valid。**
- 60/60 帧均解析到同一组 9 个 OBIS code`1-3:0.2.8``0-0:1.0.0``0-0:96.1.1`
`0-1:24.1.0``0-1:96.1.0``0-1:24.2.1``0-2:24.1.0``0-2:96.1.0``0-2:24.2.1`
equipment identifier 已脱敏,不进入 Git。
- channel 1 的 device type 是 `006`,累计值稳定为 `5.900 m³`channel 2 的 device type 是
`012`,累计值稳定为 `0.017 GJ`。同日人工表盘复核也显示 `5.900 m³` / `0.017 GJ`,正式 CLI
因而复现了该基线。
- 60 帧中未见瞬时流量、热功率、供水温度或回水温度字段。10 分钟内累计值未变化只能证明这段
时间的累计值稳定;它不能证明发生消费时的更新频率,也不能证明 reset 或 wrap 行为。
### 3.7 最终人工走查:供暖累计量变化
正式长测交付后,用户又在终端直接运行只读 probe 十几分钟并开启供暖。channel 2 的区域供暖
累计量在本次运行中从 `0.017 GJ` 增至 `0.018 GJ`,同一时刻物理热量表也显示 `0.018 GJ`
因此可以确认当前 P1 输出会在实际供暖消费下更新累计量,且 `0.001 GJ` 的变化与物理表一致。
这次人工走查没有改变完整性结论:telegram 仍缺少标准 `/`,数值与物理表一致不能替代 CRC
验证。走查也没有覆盖 reset、wrap 或精确更新延迟;这些仍须由 M8 的接纳、重复确认和告警策略
处理,而不能从一次累计量递增外推。
## 4. 正式 probe 的预期操作方式
实现后,在 workspace virtual environment 中运行只读 probe
@@ -149,7 +187,7 @@ parser 契约。是否引入 [`dsmr_parser`](https://github.com/ndokter/dsmr_par
### PRE-M8-T01 — 纯函数 telegram framing、CRC 与 OBIS parser
- **Status**: `todo`
- **Status**: `done`
- **Depends**: `none`
- **Context**: 先把串口 I/O 与解析分开,用脱敏 fixture 固定标准 DSMR 帧和本机异常帧行为。
@@ -176,11 +214,11 @@ parser 契约。是否引入 [`dsmr_parser`](https://github.com/ndokter/dsmr_par
**Acceptance criteria**
- [ ] 标准 fixture 的 frame boundary 与 CRC 可验证为 `valid`
- [ ] 脱敏本机 fixture 被标为 `unverifiable`,但能按字段而非位置解析 `m³``GJ` channel。
- [ ] 任意 chunk boundary 和字段顺序不影响结果,未知字段原样保留。
- [ ] 累计量以 `Decimal` + 原单位返回。
- [ ] `pytest tests/test_p1_probe.py``pytest``ruff check .` 全绿。
- [x] 标准 fixture 的 frame boundary 与 CRC 可验证为 `valid`
- [x] 脱敏本机 fixture 被标为 `unverifiable`,但能按字段而非位置解析 `m³``GJ` channel。
- [x] 任意 chunk boundary 和字段顺序不影响结果,未知字段原样保留。
- [x] 累计量以 `Decimal` + 原单位返回。
- [x] `pytest tests/test_p1_probe.py``pytest``ruff check .` 全绿。
**Reviewer checklist**
@@ -190,7 +228,7 @@ parser 契约。是否引入 [`dsmr_parser`](https://github.com/ndokter/dsmr_par
### PRE-M8-T02 — 只读 serial probe CLI
- **Status**: `todo`
- **Status**: `done`
- **Depends**: `PRE-M8-T01`
- **Context**: 在纯 parser 通过后增加最小 serial I/O,使真机验证可以从仓库稳定复现。
@@ -217,12 +255,12 @@ parser 契约。是否引入 [`dsmr_parser`](https://github.com/ndokter/dsmr_par
**Acceptance criteria**
- [ ] CLI 可用 `/dev/serial/by-id/...` 读取,且所有 framing 参数都可显式覆盖。
- [ ] 默认参数准确反映本机 `115200 7N1`,帮助文本说明它是实测值而非 DSMR 标准默认。
- [ ] raw output 保留原始 bytes;终端清楚区分 `valid``invalid``unverifiable`
- [ ] permission/busy/disconnect 错误非零退出并给出可执行诊断,绝不建议常驻 root。
- [ ] 依赖输入与两个生成 requirements 文件同步。
- [ ] `pytest tests/test_p1_probe.py``pytest``ruff check .` 全绿。
- [x] CLI 可用 `/dev/serial/by-id/...` 读取,且所有 framing 参数都可显式覆盖。
- [x] 默认参数准确反映本机 `115200 7N1`,帮助文本说明它是实测值而非 DSMR 标准默认。
- [x] raw output 保留原始 bytes;终端清楚区分 `valid``invalid``unverifiable`
- [x] permission/busy/disconnect 错误非零退出并给出可执行诊断,绝不建议常驻 root。
- [x] 依赖输入与两个生成 requirements 文件同步。
- [x] `pytest tests/test_p1_probe.py``pytest``ruff check .` 全绿。
**Reviewer checklist**
@@ -232,7 +270,7 @@ parser 契约。是否引入 [`dsmr_parser`](https://github.com/ndokter/dsmr_par
### PRE-M8-T03 — 正式真机验收与 M8 交接
- **Status**: `todo`
- **Status**: `done`
- **Depends**: `PRE-M8-T02`
- **Context**: 用仓库内 probe 替代本轮临时脚本,形成可重复、脱敏且能支撑 M8 Planning 的证据。
@@ -257,11 +295,11 @@ parser 契约。是否引入 [`dsmr_parser`](https://github.com/ndokter/dsmr_par
**Acceptance criteria**
- [ ] 仓库内 probe 在真机连续运行至少 10 分钟,无未处理异常退出。
- [ ] 脱敏摘要列出两个累计 channel、单位、精度、cadence 和完整性异常。
- [ ] `0.017 GJ``5.900 m³` 的基线或运行时新值与物理表再次对照。
- [ ] 明确没有从当前 P1 输出读取到瞬时流量、功率或温度。
- [ ] Pre-M8 状态与 roadmap/M8 入口同步;代码闸门保持全绿。
- [x] 仓库内 probe 在真机连续运行至少 10 分钟,无未处理异常退出。
- [x] 脱敏摘要列出两个累计 channel、单位、精度、cadence 和完整性异常。
- [x] `0.017 GJ``5.900 m³` 的基线或运行时新值与物理表再次对照。
- [x] 明确没有从当前 P1 输出读取到瞬时流量、功率或温度。
- [x] Pre-M8 状态与 roadmap/M8 入口同步;代码闸门保持全绿。
**Reviewer checklist**
@@ -277,10 +315,11 @@ parser 契约。是否引入 [`dsmr_parser`](https://github.com/ndokter/dsmr_par
- [x] GJ 和生活热水 m³ 均与物理表面板完全一致。
- [x] 已记录精度、约 10 秒 telegram cadence 和当前字段集合。
- [x] framing/CRC 失败已保留为显式异常,没有误报为校验通过。
- [ ] 仓库内 parser、fixtures、probe CLI 和自动化测试完成。
- [ ] 正式 probe 完成至少 10 分钟真机复验并产出脱敏摘要。
- [x] 仓库内 parser、fixtures、probe CLI 和自动化测试完成。
- [x] 正式 probe 完成至少 10 分钟真机复验并产出脱敏摘要。
- [x] 最终人工走查在供暖开启后观察到 `0.017 → 0.018 GJ`,并再次与物理表核对一致。
当前人工 bring-up 已足以锁定 M8 的两个累计量目标,但 Pre-M8 milestone 仍需完成 T01T03
Pre-M8 已完成并向 M8 解锁 Planning;它只交付下列真机事实,不锁定正式架构或实现任务
## 7. 向 M8 的交付物