# 决策日志 (DECISIONS) | 日期 | 决策 | 背景 | 理由 | |------|------|------|------| | 2026-07-24 | 以 Web 上位机 JS 为协议第一证据源 | 三条证据线:Web JS / APK / 固件 | Web Bluetooth JS 通常未重度混淆,可读性最高,可 fastest path 拿到 UUID 与命令格式,再与 APK/固件互证 | | 2026-07-24 | CLI 用 Python+bleak | 本地化+agent 化需求 | bleak 跨平台、无需 root 以外依赖;JSON I/O 便于 agent 编排;Pillow 覆盖全部抖动算法 | | 2026-07-24 | 项目纳入 git 管理 | 治理规则要求 | 原始目录非 git 仓库;逆向产物多、需频繁提交与回溯 | | 2026-07-24 | 倒计时前缀位图帧**不**复刻 JS 的 off-by-one | timedjs2.js:206-224 循环 `i < len-1` 丢末字节 | 判定为 JS 端笔误(设备缓冲兼容完整帧);CLI 发完整 n*32 字节,避免在末字符右下角丢 8 像素 | | 2026-07-24 | 倒计时日期/休眠小时采用 BCD 编码 | timedjs2.js:242-243 / ble.js:4013-4015 均为十进制字符串直拼后经 hexToBytes | 实测 JS 证据:month=12 → 字节 0x12,hour=23 → 0x23;与 setTime 的真 hex(intToHex)不同,需注意区分 | | 2026-07-24 | 固件头格式确认(magic 'pQ'/flags 0x01AA/长度/CRC32(body)/64B 头/0x20 填充 0x00) | 以 zlib.crc32 反推验证 CRC 字段 = body 的 CRC32,重打包字节级往返一致 | fw_pack 可生成与原镜像格式一致的文件,是固件可控(G4)的基础 | | 2026-07-24 | 中英混排 0.5 单位的倒计时前缀在 CLI 拒绝 | JS `'0'+charCount` 遇 3.5 生成非法 hex "03.5" | 属于上游 bug 路径,CLI 显式报错优于发送畸形帧 | | 2026-07-28 | 真机测试阻塞定责:USB/IP 透传链路(hci0=vhci_hcd,源 192.168.61.35:3240 Intel AX200)在 BLE 连接数据窗口 ~3s 后硬停 | btmon 两次复现:连接建立成功→MTU 交换成功(设备 RX MTU 512)→服务发现推进→~19 个 ACL 包后悬崖式断流→Disconnect 0x08;对照设备(小米温湿度计 RSSI -27)同样失败 | 排除设备与协议软件问题(扫描/连接建立/ATT 初期均正常);属 usbip-host 中断端点饥饿类故障;需在 usbip 源机原生运行或修 usbip,详见 PROGRESS.md 阻塞节 | | 2026-07-28 | **结论修正**:根因 = **大 MTU GATT 流量触发链路断流**(非此前判断的"中断端点饥饿"),具体归因于 usbipd-win/VBoxUSB/VHCI/AX200 固件链中某一环,无法唯一归因 | 熠管家在 Windows 侧排查:76B 响应完整到达 guest、下一请求已被 AX200 接收;**ATT MTU=23 时全部服务+22 个 characteristic 发现成功、无监督超时**;usbipd-win 5.3.0 + Ubuntu 6.8.0-136 均为最新 | 不能全局降 MTU(EPD 图像帧 244B > MTU23),故转向 bridge 架构:BLE 在 host-win 原生执行,guest 经 RPC 调用(见下条) | | 2026-07-28 | 引入 **bridge 能力**:host-win 原生跑 bleak(WinRT) 并以 JSONL/TCP 暴露 RPC,ubuntu-openclaw 的 ppclock 经 BridgeTransport 远程调用 | usbip 链路无法承载大 MTU GATT;Windows 原生 BLE 无此问题;NFS 仓库两边可达 | 交付:tools/bridge_server.py(单文件服务端)+ src/ppclock/bridge.py(与 BLETransport 同接口)+ CLI `--via bridge://host:port` / `PPCLOCK_VIA` 环境变量 | | 2026-07-28 | **RXTX 命令通道 = 331f(非 web 文档的 1f1f)** | 实机 GATT 诊断(bridge services op):1f1f(h=53) 仅 write 无 notify,写入 ATT-ACK 但设备不执行;331f(h=57) notify+write+read,经其实测 EFEF/模式/倒计时/停车牌全部生效 | ppclock 全栈切换(protocol.RXTX_CHAR_UUID=331f、BLETransport/BridgeTransport 写路径);保留 1f1f 为 legacy 常量 | | 2026-07-28 | **设备 ID 反解参数序实机校正**:snprintf 实参序为 MAC5,MAC4(非固件代理读的 MAC4,MAC5) | 实机 ID '81233F3C267112' 仅按 MAC5 在前才能反解出广播地址 18:BC:5A:5D:BF:28(NVDS 反转序);按原序则掩码矛盾 | protocol.recover_mac_from_device_id 已修正并锁定实机向量测试;firmware-analysis §4.2 对应行以本条为准 | | 2026-07-28 | 设备睡眠行为适配:广播窗口短(约 30-60s),连接态保持清醒 | 实测:空闲即停广播;单连接 batch 可在窗口内跑完全部命令 | 现场测试统一用 `ppclock batch` 单连接;唤醒守候循环(每 15-20s 重试)跨窗口执行 | | 2026-07-29 | **SUOTA 状态码 8/16 = 会话信息码而非失败**(仅本固件)〔本条被 2026-07-29 第 5 条(状态码分两类)取代,保留溯源〕 | 反汇编 MEM_DEV handler(0x07FCB6F8):type 0x13 命中 bit4 位测试 → 发状态 16 且 SPOTA 状态保持(0x07FCB770-0x07fcb772);type 0x12 → 状态 8 并清会话(0x07FCB780);空镜像探针与正式烧录均先收 16 后流程完好 | bridge ota 对 8/16 记录并继续,块确认只认 2;APK 错误表未覆盖 16,以固件证据为准 | | 2026-07-29 | bridge OTA 大请求需显式 StreamReader limit | asyncio 默认 64KiB 行限制,OTA 镜像 hex 请求 ~144KB 永不可达(三轮"静默停滞"根因);空镜像探针 0.6s 全通证明管线无恙 | build 9: start_server(limit=4MiB);教训:大载荷 RPC 协议设计必须考虑传输层帧限 | | 2026-07-29 | **SUOTA 状态码分两类:会话信息码(8/16)与流程错误码**;16=MEM_DEV type 0x13 位测试应答(固件 0x07FCB770),状态保持 | 反汇编 MEM_DEV handler + 三轮烧录对照(16 恒在 MEM_DEV 后 ~1s 到达,块未受影响) | bridge 对 8/16 记录继续、块确认只认 2;APK 表未覆盖 16,固件证据为准 | | 2026-07-29 | **SUOTA 状态 22/20 根因分层**:22=0x38000 产品头读电气失败(GPIO_MAP 引脚错);20=读成功但非 'pR'(引脚有效、0x38000 疑为空) | GPIO 探针(max_blocks=2 安全模式):A{00,00,06,05}→22、B{40,00,06,05}→22、C{05,06,00,40}→20;C=APK 整数 0x05060040 的 BE 字节,为唯一电气有效候选 | GPIO_MAP 采用 C(BE 序);剩余阻断=0x38000 产品头('pR')缺失/无效,见下条 | | 2026-07-29 | **0x38000 产品头阻断为 vendor 级问题**:固件在首 0x200 缓冲处理时校验 0x38000 的 'pR' 产品头(0x07FCB4AA),无效则发 20 并中止该缓冲处理;本机疑以裸 'pQ' 直启(无产品头) | 反汇编完整校验链;固件镜像内 0x3A19 处的 70 52 系代码巧合非真产品头;MEM_INFO 读回 00000000 | 强推 END 有变砖风险(END 会写 0xAA 标志+复位,裸启兜底可恢复但需 JTAG 兜底方案);暂缓强推,先与用户决策 | | 2026-07-29 | **vendor 固件目录实证**:qbsg.top /api/firmware/catalog 免认证开放,PP_da14585_4.2_CH.img 与本地镜像 sha256 完全一致(vendor OTA 镜像=本镜像,无产品头附加);NEW 变体同样引用 0x38000 检查 | 下载比对(analysis/apk/vendor_catalog.json + vendor_PP_CH/NEW.img) | 排除"vendor 镜像含产品头"假设;阻断确在设备端 0x38000 内容,非镜像差异 | | 2026-07-29 | **全量烧录实证安全性论证**:产品头校验失败时所有缓冲处理在写 flash 前中止(state[0x70] 恒 0),END 仅写 0xAA 于 [state[0x60]+2]=0x0002 而该字节原本就是 0xAA(无操作),复位后裸启原固件 | 反汇编 END 路径(0x07FCB7C6-0x07FCB7E4)与缓冲写路径(0x07FCB514 依赖中止的头部处理产物 state[0x74]) | 全量跑 OTA 的最坏结果=安全空转+复位;若中途校验通过则自然转正为真实烧录。用户授权下可执行 | | 2026-07-29 | **用户明确授权全量烧录**(301 块+END+复位,gpio_map=BE 序 C),要求全程记录与 review | 用户原话:"授权 注意做好记录和review"(此前已授权"做一下审慎测试后刷机") | 执行范围:fw_tst.img(DIS 版本串 3 字符差异);留证:状态流日志 docs/suota-flash-log.jsonl;事后验证:scan/connect/DIS 读 | | 2026-07-29 | **G4 固件可控路径选型:B(免线刷软件利用)**,放弃 UART 线刷恢复工厂布局(路径 A 文档保留备查) | 用户原话:"肯定是B";B 路径已具备:图像 flash 持久化写(0x2E000)、EF 激活 flash 写(0x3Fxxx)+ 离线 keygen、全部 BLE 功能可控、SUOTA 工具链就绪(工厂布局设备可直接复用) | 任务说明目标3 以 B 路径达标;A 路径(线刷 .bin + UART)作为可选增强留存 docs/firmware-layout.md | | 2026-07-30 | MCP 连接管理选型**方案 C:按需连接 + 空闲保持 + 串行锁 + 掉线重连一次** | 三方拉扯:设备长睡眠(广播窗口仅 30-60s)vs agent 连续操作体验(每操作重连会撞睡眠窗口)vs 电池(常连使设备保持清醒耗电) | 首次操作守候连接(窗口覆盖 30-60s 广播),操作后保持连接、空闲 `--idle-timeout`(默认 300s)自动断开让设备睡眠;一把 asyncio.Lock 串行全部操作;操作中掉线重连重试一次再向上抛;退化配置:`--idle-timeout 0`(每操作即断,最省电)或大值(近常连,体验最佳);详见 docs/superpowers/specs/2026-07-30-ppclock-mcp-design.md | | 2026-07-31 | 引入 mcp 依赖(ppclock-mcp extra)并钉 `>=1.23,<2` | 0.2.0-beta 新增 MCP 服务器层;GOVERNANCE 要求依赖引入留痕;mcp 2.0 移除 FastMCP/memory API | `<2` 防 2.0 API 断裂;`>=1.23` 因 `transport_security`(DNS 重绑定防护,`--allowed-hosts` 依赖)自 1.23 引入,且 1.23 起含 CVE-2025-66416 修复;本地与 Windows 实测均为 1.29.0 | | 2026-07-31 | 0.2.0-beta 终审裁定放行项终裁:TRANSPORT_ERRORS 含 OSError 的理论变宽、token 非恒定时间比较、Image.open 惰性解码失败归入 INTERNAL | 终审提出的三处理论风险,需终裁留痕 | 均判低风险放行:OSError 变宽仅使极端平台错误归入 BLE_ERROR(语义可接受);token 比较在可信 LAN + Bearer 场景时序攻击不可行;Image.open 惰性解码异常有 INTERNAL 兜底且 message 带类型详情 | | 2026-07-31 | `--allowed-hosts ""` 空串按 None 处理 | 空串逗号拆分得空列表,`TransportSecuritySettings(allowed_hosts=[])` 启用防护后连 localhost 族也 421(自锁) | main() 解析层 `_parse_allowed_hosts` 以 `or None` 归一,保持 mcp 默认 localhost 族;含回归测试 | | 2026-07-31 | **双 TransportError 历史包袱在 MCP 层适配,SDK 零改动** | `transports.base.TransportError` 与 `transports.local.TransportError` 同名但互不继承(历史包袱);真实 bleak 写/读路径还裸抛 `BleakError` 不包装 | 改 SDK 会动公开语义、风险大于收益;MCP 层 `device_manager.py:19-26` 以 `TRANSPORT_ERRORS` 全家捕获兜底(base/local 两类 + BleakError + OSError + asyncio.TimeoutError),掉线判定不漏形态 | | 2026-08-01 | 建立 Gitea 远端(chenwei/qianbian)并增补 Git 协作治理(GOVERNANCE §6) | 用户要求版本控制上云 + 后续治理迭代;此前仓库仅本地 | main 主干直提、分支短命、身份诚实不改写、推送前三项检查(净树/全绿/无 secret)、大文件仅原始输入随库。**远端落地**:origin=git@git.b.aeroprop.xyz:chenwei/qianbian.git(实例 git.b.aeroprop.xyz,展示域 git.banytech.com);认证=本机 ~/.ssh/id_ed25519(Gitea key 名 chenwei@bany.tech@openclaw-server);2026-08-02 全历史已推送(ls-remote 校验一致) | | 2026-08-02 | 项目级规则 review 打结:8 项不一致修复(PROGRESS 首日快照误标"最终状态"、GOVERNANCE §2 目录/§5 工具过期、PLAN P0 jadx、详细计划 Task 9 终态、DECISIONS 条目 18/20 重复标注、README 依赖缺 qrcode) | 用户要求规则 review;项目已从 0.1.0 演进至 0.2.0-beta(SDK/MCP/bridge/实机验证),多文档口径落后于现实 | 规则文件必须与交付现实一致;不改写历史(快照保留标注、重复条目保留溯源),仅修正指向与口径;GOVERNANCE 变更按 §4 留痕本条 | | 2026-08-03 | **RXTX 通道按固件家族选择**:`HM42_AIO_*` 使用写入型 `1F1F`;已验证 `PP_da14585_4.2` 继续使用 `331F`;未知固件优先 `331F` 并按 GATT 存在性回退 | HP 侧同型号异供应商设备报告 `HM42_AIO_V1.0.9`;原 SDK 写 `331F` 均返回成功但屏幕不变。GATT 枚举显示其 `1F10` 控制服务只有 write-only `1F1F`;直写 Web-BLE 序列 `e10202` + `e2` 后用户目视确认切至“日历+表” | ATT 写成功不等于设备执行。连接时读取 DIS `2A26` 并选型;仅在特征支持 notify/indicate 时订阅,避免 write-only HM42 通道误启通知。状态接口公开固件串和实际 RXTX UUID,便于运维审计 |