CM4 中继盒走 ASCII 命令 + ASCII / hex 遥测,与 legacy CM1/CM2/CM3 的二进制 0x55XX 协议根本不同。本页是 App 侧对 CM4 协议的全部已知内容;与 legacy 共有的字节级布局(15B 探针温度数据等)见 v4.2 协议规范。
范围说明:本页只讲 CM4。Legacy 在 v4.2 协议规范。两者的并排速查见 BLE 协议(概览)。
| 维度 | CM4 | Legacy(CM1/2/3 / MW3/4/5) |
|---|---|---|
| 命令编码 | ASCII(SET_F / SET_BL=...) |
二进制(0x55 AB 00 00) |
| 遥测编码 | ASCII:D=<30 hex>\r\n |
二进制:15 字节 raw |
| 遥测 payload | 解码后 = 同一 15-byte 布局 | 直接 15-byte raw |
| 状态通知 | PENON/PENOFF <id> |
0x55 AA + MAC(入仓) |
| 心跳 / 独占锁刷新 | CNT_0,每 45 秒(per Liang 2026-06-06:同机制、60 s TTL) |
0x55 B1(独占锁刷新),每 45 秒 |
| 远程通道 | BLE + MQTT/TLS | 仅 BLE |
| 探针数 | 最多 4 | 1 / 2 / 3(按型号) |
CM4↔Legacy 的家族判别由 BoosterFamily 收敛——上层代码只调 transport.setProbeTarget(...) 等家族无关签名。
GATT 服务 / 特征与 legacy 共用:
| UUID | 方向 | 用途 |
|---|---|---|
AE30 |
service | 容器(扫描时按此匹配) |
AE03 |
App → 中继盒 | write / writeWithoutResponse(命令) |
AE05 |
中继盒 → App | notify(数据 + 状态) |
CM4 用 write-with-response + notify(legacy 是 indicate)。BleService 自动按 advertised 属性选择路径。
当前 App 侧 MQTT 连接的鉴权方式并非始终固定为 deviceId + 共享密钥:当前代码会优先使用账号级 MQTT 凭据(username 为账号 uid,password 为该账号的 minted secret);只有在账号凭据不可用,或 broker 拒绝账号凭据并触发一次 legacy 回退时,才使用 deviceId + Tbroker_8f3a91(MqttConfig.devTestPassword)。这已经替代旧的 beta/123456,是 App 连接 broker 的现行实现。
WiFi 配通后中继盒连接的是当前 App 使用的自托管 Mosquitto broker;旧的 EMQX / beta/123456 凭据已退役。broker host/port 由区域配置在运行时决定,详情与区域路由见 MQTT 与云端。
| Topic | 方向 | payload |
|---|---|---|
/CM4/<deviceId>/data |
中继盒 → App | 同 BLE 的遥测 frame(ASCII D=<hex> 或 PENON/PENOFF) |
/CM4/<deviceId>/sett |
中继盒 → App(028087a / V14 / Per Beta 2026-06-16 起主要单向 box→app——先前 860de52 / TAPD #1003167 Beta 2026-06-12 起 App 也在此 publish 命令、双向;但 App 目前仍会把可镜像设置写入(SET_<color>=...、SET_C、SET_F)额外 publish 到 /sett 作为多手机同步通道,并非纯粹单向,详见 MQTT 与云端 §Topic 结构) |
中继盒主动 push 通道:物理按键改设置时的 SETT==<32hex>、WIFI_STS=N、MQTT_STS=N、<deviceId> online broker-login 公告、SET_RD 应答(详见 §SETT== 设置帧 / §unsolicited push 全集 与 MQTT 与云端 §Topic 结构) |
/CM4/<deviceId>/apps |
App → 中继盒(028087a / V14 / Per Beta 2026-06-16 新增命令 topic) | App publish 的 downlink 命令 ASCII 字符串(SET_xx=… / SET_RD / 其它 ASCII)。mqtt_service.dart:_publish 改写到这条;App 不订阅 /apps、自此再不会听见自己的命令 echo(先前两端共用 /sett、MQTT 3.1 无 no-local、App 自己的 SET_xx= 会被 broker 回弹、依赖 fall-through + 2 s _lastUserTargetWriteAt 双重 suppression;Liang 改前不考虑老设备直接切换) |
当前代码里的 CM4 命令派发已不是 BLE 优先:当设备处于 cloud-primary 时,transport 会先经 MQTT 发送命令;只有在非 cloud-primary 时才走 BLE-first,并在 BLE 不可用时回退到云端。——但 per Beta 2026-05-11(55d699a),中继盒在两边都可达时同时往两个通道推遥测,"fallback" 是 App-side 的 DeviceConnectionManager 过滤而非 wire-side 的切换。详见 MQTT 与云端 §双通道遥测。当前代码还明确把 MQTT 通道的 telemetry-stale 超时设为 60 秒;当 broker 连接仍在但 60 秒未收到 cloud frame 时,MqttService 会标记 telemetry stale 并发出 BLE fallback 信号。
App 每 45 秒发一次 CNT_0 刷新中继盒侧独占锁。BoosterFamilyExt.heartbeatIntervalSeconds 是常量来源。
Per Liang 2026-06-06(TAPD #1003176):CM4 的
CNT_0与 legacy 的0x55B1是同一独占锁机制——锁 TTL 60 秒、45 秒刷新;CM4 自有 ASCII 协议、不能接受0x55B1的二进制字节,只是 wire 不同、语义一致。c613148 之前 CM4 cadence 是 20 秒、无独占锁假设由此推翻。WiFi 模式下 BLE 链路同样要持续刷锁——否则中继盒释放锁、约 20–30 s 后主动 shed GATT 链路(即 5586d80 / TAPD #1003176 修的 "phantom Reconnecting banner" 根因)。⚠️ 此段落已过时,2026-06-06 的 BLE-primary 规则已被取代——device_connection_manager.dart顶部当前 dartdoc 明确写着:Per Liang 2026-06-29,App 现在是 MQTT-primary("supersedes the 2026-06-06 BLE-first rule, TAPD #1003176"):WiFi 已配置 + broker 可达时,云端才是活跃遥测通道;BLE 仅保留作CNT_0独占锁 +HOST=切区通道,不再是遥测来源,broker 断开时才回落 BLE。_userForcedCloud标志的语义因此也反过来——它标记"用户手动选了 WiFi 之后 BLE 恢复不应覆盖这个选择",而不是这里原先暗示的"覆盖默认 BLE 优先"。详见 CM4 协议(AI 理解版)§1 的对应 callout。
所有命令都是 ASCII 字符串。多设备并发时,第一次周期写入按 (deviceId.hashCode % cadence×1000) ms 错峰。
⚠️ AE03 写入节奏与 D= 遥测节奏(per Beta 2026-05-11,8b47f3e):同一设备相邻 AE03 写之间至少 ≥200 ms——CM4 固件无接收缓冲、命令实时处理,过短的 burst 会被丢;跨设备 GATT 链路独立,跨设备并发写不受限(上面那条
(deviceId.hashCode % cadence×1000) ms相位错开是为了避免 GATT 队列饱和,与此 per-device 规则无关)。D=<hex>遥测固件按探针单独转发、不合并,每根约 3 秒一帧——4 根全活时约 1.3 packets/s。当前 MQTT 路径对单根探针帧缺失的 stale 阈值为 35 秒。App 当前没有统一的通用写闸门,但并非“无写节流代码”:CM4 连接后已通过Future.delayed把VER、RI_CNT=?、TEMP=?以及单位同步分别错开到 300 / 600 / 900 / 1200 ms 发送,以满足同设备 AE03 写入间隔要求。,但BleService.sendCommandTo/sendRawBytesTo的 dartdoc 已警示:未来若出现连续命令流(on-connect settings sync 等)要插Future.delayed(Duration(milliseconds: 200))或加 per-device gate。
当前代码还实现了 HOST=?:用于查询中继盒当前保存的 MQTT broker host。应答不是区域编号,而是完整的 HOST=mqtts://host:port 连接串;如果设备从未写入过 HOST,代码还定义了默认回读哨兵值 Default mqtt broker!。该命令已取代旧的 USUS=? 区域查询。
| 命令 | 作用 |
|---|---|
SET_RD |
读取当前所有设置;中继盒按设置回 SETT==<32hex> 帧(per 6337d24,详见 §SETT== 设置帧) |
VER |
查询固件版本 |
USUS=? |
已退役(TAPD #1003317)——现用 HOST=? 查询完整 broker 连接串(见上方) |
WIFI_STS=? |
查询 WiFi 状态(当前代码未定义对应命令常量 / query builder) |
BL_LVL=? |
查询当前背光级别(返回 BL_LVL=N,per Beta 2026-05-11 / 55d699a) |
RI_LVL=? |
查询当前响铃音量(返回 RI_LVL=N,per Beta 2026-05-11 / 55d699a) |
RI_CNT=? |
查询当前响铃时长档位——旧固件不应答(7c141d7 / TAPD #1003203 log 实证:曾每条 RI_CNT 都是 App→booster、零 reply);per Liang 2026-06-10 起固件回报 RI_CNT=N,App 经 on-connect RI_CNT=? + onRingCountReported bridge 回读到 buzzerRingModeProvider。✅ RI_CNT=4(Mute)回读已修(commit 6cfc6ee2,2026-06-23):_parseRingCount 的合法范围从 0..3 放宽到 0..4,Mute 状态现在能正常经 readback 采纳,不再是已知 bug(详见 §显示亮度 / 音量 / 响铃时长) |
| 命令 | 作用 |
|---|---|
SET_F |
中继盒物理屏切换到 °F |
SET_C |
中继盒物理屏切换到 °C |
注:与 legacy 不同,SET_F / SET_C 是全局的,不带探针参数。
| 命令 | 作用 |
|---|---|
RING_OFF |
静音当前响铃——⚠️ buzzer-only(Beta Q12:静音当前响铃,不影响后续告警)。不清除每探针 闹钟 icon + backlight flash;Mute(RI_CNT=4)/ timed ring(RI_CNT=2/3)已过期等 booster 不在响时本身就 no-op,Confirm 路径必须再发 SET_{COLOR}=000000 才能清掉视觉报警(3c40aa9 / TAPD #1003214,详见 §探针配置 字段 A) |
SET_<color>=000000<G> |
探针级报警 DISARM——字段 A=0、BCDEF 全 0(FW 关 A=0 后丢弃 tail,Beta 2026-05-25 cloud doc 表给出 SET_BL=000000 范本)。3c40aa9 / TAPD #1003214:App 在 CM4 confirm 路径上把它当作物理按键的 wire 等价用——muteAlarm 先发 RING_OFF、Future.delayed(250 ms)(per Beta 2026-05-11 的同设备 AE03 ≥200 ms 间距规则)后调 disarmProbeAlarm(probe) 关闭被确认的探针。其它探针不受影响(CM4 SET_<color> 无 legacy 0x55AD 的 all-probe collateral clear,无需 re-arm pass)。HW 验证 pending Liang:in-flight SET_<color>=000000 是否真能在中继盒侧清掉正在 firing 的 icon + backlight;若固件忽略 alarming 状态下的 A=0,fallback 是 FW 改让 RING_OFF 在 mute 模式下连同视觉一起清 |
SET_H=<n><0\|1> |
探针 <n> 的高温报警开关,<n> ∈ |
SET_L=<n><0\|1> |
探针 <n> 的低温报警开关 |
CLK=<n><0\|1> |
探针 <n> 的计时器显示开关 |
| 命令 | 作用 |
|---|---|
CNT_0 |
独占锁刷新(每 45 秒,per Liang 2026-06-06;与 legacy 0x55B1 同机制 / 60 s TTL) |
BT_STS=<0\|1> |
主动告知中继盒当前 BLE 连接状态 |
补充:当前代码已把 SET_<probe>=ABCDEF 扩展为可选的 7 字符形态 SET_<probe>=ABCDEFG。新增字段 G 表示 cook work-state:0 = stop、1 = start、2 = keep(保持当前 cook 状态不变)。未携带 G 时 CM4Command.configureProbe 仍兼容 6 字符;但 Cm4BoosterTransport.setProbeTarget 实际总传 workState ?? Cm4WorkState.keep,故 App→中继盒 probe-config 写现为 7 字符(末位 G=2 keep)。
SET_<probe>=ABCDEF
| 字段 | 值 |
|---|---|
| A | 报警模式:0 = 关 / 1 = 响铃(armed)。仅二值,固件丢弃任何 A∉{0,1} 的整条命令(738fdeb / TAPD #1003233,Beta 2026-06-11:log 实证 SET_BU=214F25 Save 发出后无 SETT== ack 也无 LCD 更新,而同探针 A=1 写 247 ms 内 ack)。先前 065d153 / TAPD #1003138 引入的 2 = 静音仅背光(per-cook silent mirror,a5cf346 / TAPD #1003217 用作 Mute 镜像写入)已整体回退:cm4_protocol.configureProbe 删 silent 参数、A 改为 alarmEnabled ? 1 : 0,BoosterTransport / Cm4BoosterTransport / LegacyBoosterTransport / BleDeviceService.setProbeTarget 沿栈剥除 silent,cooking 页 _pushTargetToDevice 不再读 silentAlarm、#1003105 dedup signature 也不再随 Buzzer Mute 变化,booster_configurations_page buzzer pick 不再 re-push active cook target。Mute 现纯走 booster-level RI_CNT=4。SETT== 回报 A 仍读 0/1;parser 保留 bytes[5] != 0x00 而非 == 0x01(防御老固件 fallback 值仍被读作"armed"而不会被 #1003117 ack 检测误读成 device-MUTE 1→0 edge),见 §SETT== 设置帧;CM4SettingsAscii.silent 字段 + parser 一并删除。⚠️ DISARM 形 SET_<color>=000000(A=0 + BCDEF 全 0,Beta 2026-05-25 cloud doc 范本):FW 关 A=0 后丢弃 tail,3c40aa9 / TAPD #1003214 起 App Confirm 路径用它当物理按键的 wire 等价(pair 在 RING_OFF 后 250 ms)清掉每探针 闹钟 icon + backlight,详见 §报警 / 静音 |
| B | 单位(0 = °F, 1 = °C) |
| CD | 目标温度(两位大写十六进制,00–FF,per Beta 2026-05-11) |
| E | 肉种(1–9) |
| F | 熟度(1–5) |
<probe> 前缀按颜色(per cloud doc 2026-05-19,6e5dfb7 反向恢复色名):
| 探针 | 颜色(V5) | wire 前缀 |
|---|---|---|
| Probe 1 | Black | SET_BL |
| Probe 2 | White | SET_WH |
| Probe 3 | Blue | SET_BU |
| Probe 4 | Yellow | SET_YE |
其它 per-probe 命令(SET_H=XY / SET_L=XY / CLK=XY)的 X 字段当前代码使用数字 slot 0–3——这个不对称在协议本身。
⚠️ SET 前缀历史(per cloud doc 2026-05-19,6e5dfb7):原 spec / 早期 App 一直用色名前缀;Beta 2026-05-11 Q12 一度说"1-digit
SET_1..4canonical, 色名 retired"(55d699a 落地);Beta 2026-05-12 Q3 再改成 2-digitSET_01..04(052e2ce / ac25aec);Beta 2026-05-13 walk-back;2026-05-19 cloud doc 明确给出色名形式 +SET_BL=013211示例 + Liang 2026-05-16 HW test (culinatech_log_20260516201644.txt) 实测 numeric form booster 不应答 SETT==、目标不生效——6e5dfb7 反向恢复色名前缀。
例:把 Probe 2(White)设到 70°C,肉种 3,熟度 4 → SET_WH=014634(70 → hex 46);165°F 鸡 → SET_<color>=00A553(165 → hex A5)。整串字符串以大写 emit(hex 字母仅在 CD 槽位才会出现 A–F)。
补充:当前 App 代码已经为背光级别接入了按设备持久化的本地状态源 boosterBacklightLevelProvider,默认值是 2。代码注释同时明确写明:背光也可由中继盒物理按键修改,但 SETT== 16-byte 帧不含背光字段(_parseSettAscii 只解析至字节 15 的 E/F),不会经 SETT== 回传;cm4_data_parser 亦无 BL_LVL=N 上行解析。设备真相应走 on-connect BL_LVL=?(CM4Command.queryBacklight 已定义),读回桥接尚未接通,因此本地记录在物理按键改动后可能变 stale,后续需补 BL_LVL=? 的 on-connect read-back bridge。
| 命令 | 范围 | 作用 |
|---|---|---|
BL_LVL=N |
N ∈ | 背光级别 |
RI_LVL=N |
N ∈ | 响铃音量 |
RI_CNT=N |
N ∈ {0, 1, 2, 3, 4} | 报警响铃时长档位 / 静音:0=持续 / 1=3 声 / 2=1 分钟 / 3=5 分钟 / 4=静音(Mute,per Liang 2026-06-10:device-level Mute 是 RI_CNT=4,中继盒保存并对每次新 cook 自动应用——RI_CNT 自身承接 Mute、不再走「#1003138 A 字段」 那条路径)。a5cf346 / TAPD #1003217 曾让 App 在 pick Mute 时镜像写入 active + newly armed cook 的 per-probe SET_<color>= A 字段为 silent(A=2)让中继盒 LCD 在已 armed 的 cook 上也渲染静音/斜线报警 icon,但 738fdeb / TAPD #1003233(Beta 2026-06-11)整体取消——固件只接受 A∈{0,1}、任何 A=2 都让整条命令被丢弃(实证 SET_BU=214F25 Save 发出后无 SETT== ack 也无 LCD 更新),Mute 自此纯走 booster-level RI_CNT=4、不再下传 per-cook。先前 3be4358 把 RI_CNT 误绑成 #1003138 后 c8d5ed5 整体回退、Liang 2026-06-09 澄清 RI_CNT 是独立命令后 2f3e778 重新接入;2026-06-10 reframe 进一步把 Mute 归入 RI_CNT=4,2026-06-11 把 mirror sink 也一并撤除(详见 §探针配置 A 字段) |
⚠️ RI_CNT 是 App-only-editable 字段——物理按键不能改、SETT== / D=<hex> 遥测里不带、无 unsolicited push。7c141d7 / TAPD #1003203 起 App 侧 source-of-truth 改为本地持久化 buzzerRingModeProvider(StateNotifierProvider.family / SharedPreferences key buzzer_ring_mode_<deviceId>,默认 0=持续——booster 自身上电默认,per Liang 2026-06-09):2026-06-10 之前固件保存 RI_CNT=N 但不回报(log 实证:每条 RI_CNT 都是 App→booster、零 reply,含 RI_CNT=?),connect 末尾 RI_CNT=? + _handleRingCount → Booster.ringCount 回读链路因此一度 de-facto dead,Booster.ringCount 字段永不更新;Xiaomi checkmark 隐形、Pixel checkmark stale 的根因就是这个 missing reply。per Liang 2026-06-10 起固件回报 RI_CNT=N,回读链路经 onRingCountReported bridge 恢复(见下)。改后 BoosterConfigurations Buzzer 行 sheet 的 ✓ 选项与 row subtitle 直接读 buzzerRingModeProvider 即刻同步、各机型一致;每次 tap 同时 set() 持久化 + 发 RI_CNT=N 给中继盒。5-option sheet 已接入(Continuous / 3 Beeps / 1 Minute / 5 Minutes / Mute,per Liang 2026-06-10 Mute 由 RI_CNT=4 承接)。⚠️ 738fdeb / TAPD #1003233(Beta 2026-06-11)撤掉 a5cf346 的 SET A=2 镜像——pick Mute / 切回非 Mute mode 不再按 active CM4 cook 队列遍历调 transport.setProbeTarget(..., silent: …);_sendBuzzerModeSequence 缩回纯 RI_CNT=N + 400 ms 间距 + RI_CNT=? verify 的 set + paced verify 两步,_activeSessionsForBuzzerRefresh / CM4_BUZZER_MODE_TARGET_REFRESH 日志一并删。⚠️ c002576 / TAPD #1003273 起进一步缩到 RI_CNT=N 单条:booster 每收一条 RI_CNT 都 beep 一声,set + 400 ms 后 RI_CNT=? verify 让箱子响 "di----di"(WiFi 模式两条都 hit /sett,偶尔 MQTT message coalesce 只响一声 "di")。_sendBuzzerModeSequence helper 与 ble_device_service.dart import 整体删,pick 直接 sendBoosterSettingTo(deviceId, CM4Command.setRingCount(mode)) 一发一停;设备真相走 on-connect RI_CNT=? 查询 + onRingCountReported bridge(per Liang 2026-06-10),本地 optimistic pick 撑到那时;RI_CNT 是 App-only-editable 字段、刚发的值即权威。⚠️ 回读目前只覆盖 0–3 档:_parseRingCount(cm4_data_parser.dart)cap 在 0..3,RI_CNT=4(Mute)回报被丢弃、Mute 状态不经 readback 采纳——已知 bug、待把 parser 放宽到 0..4。理由:固件只接受 A∈{0,1},A=2 让整条 SET_<color>= 被丢弃(实证 SET_BU=214F25 无 ack);Mute 自此纯走 booster-level RI_CNT=4、不再下传 per-cook。详见 仪表盘 §配置齿轮。
当前代码已实现显示模式查询命令 TEMP=?。中继盒回读当前模式时,返回 TEMP_INT / TEMP_EXT / TEMP_ALL 之一;协议层已把这三种回包当作 TEMP=? 的应答来解析,因此该功能不再是纯 write-only。
| 命令 | 作用 |
|---|---|
TEMP_INT |
中继盒 LCD 大数字显示探针内温(LCD 角标 -INT) |
TEMP_EXT |
中继盒 LCD 大数字显示探针外温 / 环境温(LCD 角标 -EXT) |
TEMP_ALL |
在内 / 外温之间自动循环 |
当前已支持 TEMP=? 查询:中继盒以 TEMP_INT、TEMP_EXT 或 TEMP_ALL 回读当前模式,协议层会解析该应答;该模式仍不包含在 SETT== / D=<hex> 遥测中。Per Liang 2026-06-10(97fe331 / TAPD #1003204 sub-correction):模式可以在中继盒物理按键改(先前 efd6ce8 wiki 写的「物理按键也改不了 / App 是 sole writer」是错的),且跨开关机保留,出厂默认 = TEMP_ALL(Cycle)。boosterDisplayModeProvider 因此从 StateNotifierProvider.family<…, String?, …> 改为 StateNotifierProvider.family<…, String, …>、initial state 由 null 改为 CM4Command.displayCycle 作为 factory-default seed——fresh install / 同一账号第二台手机首次 pick 前 sheet ✓ 与 row subtitle 也会先显示 Cycle 而非"未设"。Best-effort 兜底:用户在物理按键改了之后 App 侧记录就 stale,待 FW 查询命令上线才补完整真相。每次 tap 经 BleDeviceService.sendBoosterSettingTo(deviceId, CM4Command.displayInternal / External / Cycle) 发出 + boosterDisplayModeProvider.set(mode) 持久化 + snackbar 确认(⚠️ fc7e379 / TAPD #1003254 起:DISP 与 Buzzer RI_CNT 都从 raw sendCommandTo / sendCommandAsyncTo 改走新增的 sendBoosterSettingTo → BoosterTransport.sendSetting → CM4 _send 的 BLE-or-cloud fallback,BLE 链路不在时经 /CM4/<id>/sett 推送送达;先前 raw 路径是 BLE-only、WiFi 模式 + 蓝牙关时静默吞掉、中继盒永远收不到 buzzer / display 切换。WiFi-provisioning 写入 M_ID / M_PD / SSID / PSWD 故意留在 BLE-only 路径),详见 仪表盘 §配置齿轮。
当前代码还实现了 WIFI=0 / WIFI=1:这是BLE-only 的 WiFi 射频开关命令,不是 WIFI_STS= 状态上报。当前代码已不再发送 WIFI=0。切到 WiFi(Cloud)时,只有在中继盒当前自报 WiFi 为 down 时,App 才会通过 BLE-only 发送 WIFI=1 去唤醒曾被旧版 App 关闭 WiFi 的设备;若中继盒自报 WiFi 已 up,则会跳过该命令,因为对已在线设备发送 WIFI=1 会把其中继盒的 WiFi/MQTT 栈从 broker 上弹掉。WIFI=0/1 这类射频开关命令都只能走 BLE。
| 命令 | 作用 |
|---|---|
M_ID=<deviceId> |
设置中继盒自己登录 MQTT broker 用的 username(per Beta/Liang 2026-06-12,88aaeaa)。不重启中继盒。⚠️ 不发该命令时固件会 fallback 到内置 rd broker-superuser 账号——server 端 topic / auth 错误都被吞掉。详见 §配网序言下方 callout |
M_PD=<password> |
设置中继盒登录 MQTT broker 用的密码(同上 88aaeaa)。不重启。值取 MqttConfig.devTestPassword 共享 dev secret;dev log 仅记长度、不记明文 |
SSID=<name> |
设置 WiFi 名 |
PSWD=<password> |
设置 WiFi 密码(收到后中继盒重启到 WiFi 模式) |
WIFI_STS=N |
设置 WiFi 状态指示,N ∈ |
⚠️ 配网序言:M_ID → M_PD → SSID → PSWD(per Beta/Liang 2026-06-12,88aaeaa):App 必须先按顺序写
M_ID=<deviceId>+M_PD=<MqttConfig.devTestPassword>、再写SSID=+PSWD=,每两条之间 ≥1 s 间距满足 AE03 同设备 write-pacing。前两条只设 broker 凭据、不重启中继盒;只有PSWD=触发 boot。M_ID=/M_PD=wire 格式假定与SSID=/PSWD=一致——未到位的 written 确认前,固件对未知写直接 ignore(与 today's behavior 等价),落地后会再校正。dev log 记WIFI_PAIR_MQTT_CRED_SENT(只带密码长度)。详见 WiFi 配置 §代码路径。
当前 HOST=<url> 是 BLE-only 命令,不得经 MQTT 云端发送;错误的 host 若写入会使中继盒滞留在不可达 broker。代码注释还说明 HOST= 只会让中继盒重连 broker,不会重启中继盒,也不会断开 BLE/GATT;真正会触发重启的是 PSWD=;M_PD= 只写入 MQTT 密码,不会重启中继盒。
| 命令 | 作用 |
|---|---|
HOST=<url> |
设置 MQTT broker host,写入完整连接串(例如 mqtts://cm4-hk.culinatech.shop:8883),不是区域编号;该命令已替代退役的 USUS=N |
OTA |
进入 OTA 升级模式 |
D=<hex> 探针遥测ASCII 字符串:D= 后当前解析器要求的是至少 30 个 hex 字符,并不是只接受固定 30 个字符。
另外,当前解析器会在 trim() 前先移除 BLE 通知尾部的 \x00 填充字节。代码注释说明 V18 固件可能在 \r\n 终止符后补 NUL 到固定宽度;若不先剥离,后续 hex 解码会因尾部 \r\n\x00... 而抛出 FormatException,导致探针遥测无法更新。
当前代码还已接入 V19+ 的 18 字节 D= 遥测:除兼容 legacy 的 15 字节内容外,额外的 cook timer 会被解析并透传到 Probe.cookTimer;旧 15 字节帧该字段为 null。因此本节后面的 15 字节表只覆盖兼容部分,不再代表当前所有 D= 帧长度。
| 字节 | 字段 | 解析 |
|---|---|---|
| 1 | 中继盒电量 | 0–10 等级;高 nibble = 8 表示充电中 |
| 2 | 中继盒固件版本 | hex → decimal,显示 V<n> |
| 3–4 | 探针内温 ADC | BE16 → tempIntArray 反查 °C |
| 5–6 | 探针环境温 ADC | BE16 → tempExtArray 反查 °C |
| 7 | 探针电量 | 0–10 等级 |
| 8 | 探针固件版本 | hex → decimal |
| 9–14 | 探针 MAC | 6 字节,on-wire 反序;字节 13(index 4)= 探针标识 |
| 15 | RSSI | -(0x100 - byte) dBm |
字节序细节与 NTC 查表见 v4.2 §三、字节序 与 §四、温度查表(CM4 与 legacy 共享 lib/core/protocol/temperature_lookup.dart)。
PENON <id> / PENOFF <id> 探针上线 / 下线格式:
PENON 1 / PENON 2 / PENON 3 / PENON 4(新协议)PENOFF 形式相同(当前固件仅发 PENOFF <N>,无 PENON push)。Parser 仅接受数字 1–4 映射到 ProbeNumber enum,不接受颜色名。WIFI_STS=N WiFi 状态广播 ⚠️ 已知断口当前 cm4_data_parser 已解析 WIFI_STS=N,并返回 CM4WifiStatus;若固件附带第二行 RSSI=<n>dB,解析器也会一并提取 rssiDbm。push-on-change vs query-response-only 语义 Beta 还没回——记为 Q5 follow-up。若 Beta 确认是 unsolicited push,再加 Cm4WifiStatus message 类型接进 parser。✅ 闭合(Beta 2026-05-12 / 9f419a8 V13 整合):Beta 确认 WIFI_STS=N 是 unsolicited push(2 s 一次)、CM4WifiStatus 已进 parser、_handleWifiStatus → Booster.wifiStatus;860de52 / TAPD #1003167 起 MQTT /sett 通道也带、4f26473 起 dashboard WiFi glyph 无条件渲 wifiStatus.firmwareValue。V13 同时新增 MQTT_STS=N(10 s 一推、broker 健康)作为 Beta 指定的「配网成功」信号——WIFI_STS 只报 AP join、MQTT_STS=1 才报 broker 可达。
SETT==<32 hex> 设置帧(per cloud doc 2026-05-11,6337d24)当前 /CM4/<deviceId>/sett 还承载多手机设置镜像:App 除了会 mirror SET_<color>=...、SET_C、SET_F 外,还会 mirror booster-level 设置写入:BL_LVL=N、RI_CNT=N、RI_LVL=N、TEMP_INT / TEMP_EXT / TEMP_ALL。接收侧若解析不到 CM4Message,会再按 peer-setting mirror 路径尝试 adopt。其中 SET_<color>=... 通过 ProbeConfigCommand.tryParse(...) 回写 target / meat / doneness,SET_C / SET_F 则走全局单位同步;本机自己刚发出的 mirror echo 仍受 2 秒抑制窗口保护,不会被当成对端改动重复采纳。
补充:当前 BLE 代码注释明确区分了两种到达方式。物理按键改设置时,是“每个受影响探针单条 SETT==”的 unsolicited push;而 App 主动发送 SET_RD 时,会收到 4 条连续 SETT==,每个 probe slot 一条,handler 会逐条分发处理。
32 hex chars 解码出 16 字节,是中继盒对 SET_RD 查询的响应;当前代码注释还表明,用户在物理按键改设置时会在 BLE 与 MQTT 两条通道上收到同类 unsolicited SETT== 帧。
| 字节(0-idx) | 字段 |
|---|---|
| 0 | 中继盒电量 raw(过 decodeBoosterBattery) |
| 1 | 中继盒固件版本 |
| 2 | 单位(00 = °F,01 = °C) |
| 3–4 | 预设报警 raw(LE:byte 3 低位 / byte 4 高位) |
| 5 | 报警启用(00 = off,01 = on) |
| 6 | 报警值 °F |
| 7 | 报警值 °C |
| 8–13 | 探针地址(6 字节 wire 序,addr[4] = 探针标识) |
| 14 | 肉种 E(1–9;1 = Custom,per cloud doc 编号见 §Q2) |
| 15 | 熟度 F(1–5;仅 E>1 时生效) |
App 侧解析为 CM4SettingsAscii 类型;handler 当前接入两路 adopt——9240a3b 起 target adopt(CM4_ALARM_SYNC_ADOPT,复用 legacy 的 diverge-then-adopt + echo-suppression on lastUserTargetWriteAt),40f3340 起 meat-type adopt(CM4_MEAT_SYNC_ADOPT,per-probe _DeviceState.cachedMeatTypeE 抗 echo + 共享同一个 2 s 用户写入窗口,TAPD #1003050 ①);doneness 当前也已 adopt:cloud 路径会把 SETT== 的字节 F 解析为 Doneness,并在通过门控后调用 onProbeDonenessReconciled 回写到上层状态。但 alarm 配置并非仅记录日志,当前代码会从 SETT== 构造 ProbeAlarmConfig 并通过 onProbeAlarmConfigUpdated 桥接到 provider。
✅
MQTT 订阅尚未连通:App 只 publish 到860de52 / TAPD #1003167(Beta 2026-06-12)整体接通:/CM4/<id>/sett,并不 subscribe;要接 unsolicited 还得加 prefix-filter 避免把 App 自己 outbound 的命令被 broker echo 回来当成新设置吃。MqttService在 connect 与 auto-reconnect 时subscribe/CM4/<id>/sett,_handleSettingsAsciicloud-local 镜像沿用 BLE 路径同套 Liang 闸(alarm-inactive skip / factory-reset one-shot ADOPT / 2 s own-echo suppression / strict< 1.0 °Fmatched check),三回调onProbeAlarmConfigUpdated/onProbeTargetReconciled/onProbeMeatTypeReconciled在device_providers.dart接进与 BLE 同一 provider body;App 自发的SET_xx=echo 不解析成CM4Message、静默 fall through,无需 prefix-filter。WIFI_STS push 同走/sett、merge 到Booster.wifiStatus让 cloud 模式 dashboard WiFi glyph 实时(详见 MQTT 与云端 §Topic 结构 旁的 ✅ callout 与 仪表盘 §header WiFi glyph)。BLE 路径下的周期SET_RD轮询仍是独立 follow-up(与本次 MQTT 订阅修复正交)。
Ver=V<decimal>+MAC<xx:xx:xx:xx:xx:xx> 版本 + WiFi MAC 应答(per Liang 2026-06-04,TAPD #1003141)当前代码按 V<decimal> 解析 VER 应答里的版本号,并将其视为 ESP WiFi 芯片版本;它不是这里所写的 V<hex>,也不按“与 D= 固件字节同义”处理。+MAC.. 段仍是中继盒的 WiFi 网卡硬件 MAC。App 在 CM4 连接末尾延迟 300 ms 发 VER(BleDeviceService._finishConnect,间距 SET_RD ≥ 200 ms 守 AE03 同设备 write-pacing),解析进 CM4VersionInfo 后由 _handleVersionInfo 写到 Booster.wifiMac,仪表盘 ℹ 信息对话框由「—」placeholder 翻为实际 MAC(详见 仪表盘 §Action #5 ℹ 信息)。固件版本字段 redundancy 不用,主取 MAC。Ver= 是solicited 应答(与 SETT== 既 unsolicited push 又 SET_RD 应答的双语义不同——它仅作 VER 的应答),所以不进上方「unsolicited 全集」清单;老固件回复缺 +MAC 段时 wifiMac == null、对话框保留 placeholder。BoosterNotifier.snapshotsEqual 同时比 wifiMac 保证这条 ~300 ms 后的单次 emit 不被 dedup 吞。
补充:当前解析器还支持 OTA=N% OTA 进度上报。该消息会在 App 发送 OTA 后由中继盒上行,代码按 0–100 解析百分比,并明确兼容 BLE 与 MQTT /sett 两条通道。
Per Beta 2026-05-11(Q6 确认 + 6337d24 SETT== 接入):上述三种(D=<hex>、PENON/PENOFF <id>、SETT==<32hex>)曾是 CM4 固件 unsolicited ASCII 推送的全集。中继盒/探针低电量、过温与安全报警条件全部是 App 端从 D=<hex> 解出的派生量(中继盒电量看字节 1、探针电量看字节 7、内/环境温看字节 3–6 配合 SET_H / SET_L 阈值),没有专门的 FW push。⚠️ 9f419a8 / Beta 2026-06-15(V13)扩 unsolicited 集——上述三种之外新增 WIFI_STS=N(2 s 一推、AP join 状态,先前 4ece91f Q5 断口处理已闭合;⚠️ decf29b / TAPD #1003141 0613:V13 起 WIFI_STS=N 第二行追加 RSSI=<n>dB——典型 wire WIFI_STS=3\n RSSI=-62dB,0615 log 上 _parseWifiStatus 拿整尾 int.tryParse → null 把每条 push 静默丢,parser 改 regex 分别抓 bar bucket + 可选 RSSI=<n>dB,CM4WifiStatus.rssiDbm → Booster.wifiRssiDbm,info dialog 拿到真实 dBm;老固件无 RSSI 段时 rssiDbm == null fallback 到 bar 区间,详见 仪表盘 §Action #5 ℹ 信息 6458c32 callout 末尾的 decf29b 沿用)、MQTT_STS=N(10 s 一推,N∈{0,1}:1 = booster 自己 broker session 健康即 Beta 指定的「配网成功」信号,与 WIFI_STS 的 AP join 区分——盒子可能 WiFi 满格却到不了 broker、走 CM4MqttStatus 进 _handleMqttStatus 更新 Booster.mqttConnected、BoosterNotifier.snapshotsEqual 已加 mqttConnected 字段比较防 false→true 翻转被去重吞)、M_ID OK / M_PD OK(配网时 App 写 M_ID= / M_PD= 落 NVS 后的 echo ack,单次一帧、走 CM4CredAck 进 _handleCredAck 记 always-on WIFI_PAIR_CRED_ACK 日志确认凭据真持久化,仍是 diagnostic 而非配对 verdict 判据)。⚠️ 028087a / V14 / Per Beta 2026-06-16 再扩 unsolicited 集:新增 <deviceId> online broker-login 公告——中继盒一登录 broker 就向 /sett publish " online",Per Liang 这是最可靠的 online / 配网成功信号(与 MQTT_STS=1 双保险);parser 大小写不敏感 + 容忍 CRLF 尾,map 到 CM4MqttStatus(connected: true) 走与 MQTT_STS=1 同套 Booster.mqttConnected 路径,wifi_setup_page 配网 verify 与 dashboard 现有的 90 s 双信号或 verdict 窗自动接通、零下游改动。同 commit V14 命令 topic 拆分 /CM4/<id>/apps(详见上方 §通信通道 MQTT 表),先前 App 自己的 SET_xx= echo 回弹问题自此从根本消除。Liang 不做老固件兼容门控,"直接改、不考虑老设备"。其它形式的 ASCII 进 parser 直接静默丢弃(log PARSER unrecognised)今后均视为 routing bug 或固件回归。Solicited 应答有 SETT== (SET_RD 触发,9f419a8 起 cloud 路径也支持:MqttService 在首条 cloud 帧到达后 publish SET_RD 到 /sett、Beta 2026-06-15 确认中继盒 over MQTT 也回 SETT==、_handleSettingsAscii adopt——纯 cloud session 无 GATT 也能同步 target / alarm) 与 Ver=...+MAC... (VER 触发,per Liang 2026-06-04 / TAPD #1003141,详见上方) 两种。
App ↔ CM4 中继盒
SET_RD / VER / HOST=?(原 USUS=?,已退役) / WIFI_STS=? ─────────►
SET_F / SET_C ─────────►
RING_OFF ─────────►
SET_H=<n><01> / SET_L=<n><01> ─────────►
CLK=<n><01> ─────────►
CNT_0(每 45s 独占锁刷新) ─────────►
BT_STS=<01> ─────────►
SET_<color>=ABCDEF ─────────►
BL_LVL=N / RI_LVL=N / RI_CNT=N ─────────►
TEMP_INT / TEMP_EXT / TEMP_ALL ─────────► (write-only,b4592c5)
SSID=... / PSWD=... / WIFI_STS=N ─────────►
HOST=<url>(原 USUS=N,已退役) / OTA ─────────►
◄───────── D=<30hex>\r\n
◄───────── PENON / PENOFF <id>
◄───────── SETT==<32hex> (SET_RD 响应 + 物理按键改设置 unsolicited push,6337d24)
◄───────── WIFI_STS=N(2 s 一推,AP join 状态,Beta 2026-05-12 闭合 Q5)
◄───────── MQTT_STS=N(10 s 一推,broker 健康,Beta 2026-06-15 V13 = 配网成功信号)
◄───────── M_ID OK / M_PD OK(配网时 NVS 持久化 echo ack,Beta 2026-06-15 V13)
以下问题在新人触碰 CM4 协议层时容易踩坑。优先级按对当前用户体验的影响排。
Per Beta 2026-05-11 spec:<CD> 是两位大写十六进制(00–FF),不是十进制——0xFF=255 覆盖所有现实烹饪温度(鸡 165°F → A5、火鸡 175°F → AF、牛排全熟 160°F → A0、°C 槽 70 → 46),早先把 chunk 当十进制读才会得出"装不下 99°F+"的结论。CM4Command.configureProbe 改用 toRadixString(16).padLeft(2, '0') 编码并对整串走 .toUpperCase()(hex 字母仅 CD 出,但整串大写防御性 emit),Cm4BoosterTransport.setProbeTarget 的 skip 守卫从 > 99 放宽到 > 0xFF。Beta 同次确认 sub-answer (a)/(b)/(c) 全部不需要:booster 按 B flag 自动 reconcile 显示单位,CD 槽位 2 字符上限 0xFF 不变,也没有备用的高温命令路径。
meatType 1–9 / doneness 1–5 的具体映射 ✅ 已对齐(cloud doc 2026-05-11 / 351430f)Per cloud doc 2026-05-11(351430f 反向同步):<E> 按 cloud doc 的 (E,F)→temp 查表索引编号,与中继盒物理屏槽位无关——1=Custom("无 preset" sentinel,FW 不查表、用 App 发的 CD 原值)、2=Veal、3=Beef、4=Chicken、5=Lamb、6=Hamburger、7=Pork、8=Fish、9=Turkey;<F> ∈ {1..5} 自然顺序(Rare → Well Done),仅当 E>1 时生效(E=1 Custom 下 F 被 FW 忽略)。E>1 时 FW 用 (E,F) 表算 target、完全忽略 App 的 CD(这条由 e43ba81 + Beta 2026-05-12 Q5 sub-answer (a) 明确确认,详见下方 ⚠️ callout)。中继盒物理屏视觉上把 Custom 放在右下槽位(位置 9)只是 LCD UX,与 wire E 编号无关——把屏序号当 wire E 是 7356689 引入的 bug,351430f 已反向修正;Cm4BoosterTransport.setProbeTarget fallback 默认 (meatType=1, doneness=3),即"缺 meat ⇒ Custom + medium,让 App 的 CD 直接生效"。
⚠️ CD 字段语义 + 不合法 (E,F) 组合(per Beta 2026-05-12,e43ba81,Q5 follow-up):preset 激活(E=2..9)时 FW 直接读 cloud doc 的 (E,F) → target 查表,完全忽略 App 发的 CD——Beta 在 (a)/(b)/(c) 三个 sub-answer 里明确选 (a):FW 总是用 table、不是 App-CD-override、也不是"table 决报警 / CD 决屏显",所以 preset 场景下 App 不需要也不应该尝试"算个 smart CD",CD 对 E=2..9 是 don't-care;只有 E=1(Custom,sentinel)会把 App CD 当 target 直发、F 被 FW 忽略。cloud doc 的 (E,F) 表里有 "--" 单元(如 Chicken+Rare),Beta 明确指示"请不要发送"——booster 对未填组合行为 undefined;新加的
MeatTypeFwTable.validDonenessextension(probe.dart)按家族返回合法Doneness列表,约定 N 个合法 doneness 一定是 Rare→Well Done 末尾 N 个——Veal/Beef/Lamb (E=2/3/5) 全 5 个、Pork (E=7) 去 Rare 剩 4 个、Fish (E=8) 只 Medium/MW/WD 3 个、Chicken/Hamburger/Turkey (E=4/6/9) 只 Well Done 1 个、Custom (E=1) 豁免暴露全集。data 层已铺好,但 cooking page / preset page picker 的接入尚未做(pre-existing UI gap,不在 e43ba81 范围内);新增 8 个test/models/probe_test.dart测试覆盖 numbering round-trip、四种约束模式、"valid 集合是Doneness.values的连续后缀"不变式。
Legacy 通过 12B 设置响应(字节 6 = enabled flag、字节 7 = °F 参考、字节 8 = °C 参考)实现 ALARM_SYNC_ADOPT:用户在中继盒按键改目标后,App 收到下一帧 12B 时把目标拉回到 App 侧。Legacy 每 ~3 秒自动 push 一次 12B、无需 App 主动查询,所以同步是常态。
CM4 端的 wire 等价由 SETT==<32hex> 承担(详见上方 §SETT== 设置帧 / cloud doc 2026-05-11 / 6337d24)——/CM4/<id>/sett MQTT topic 在用户改设置时被中继盒主动 push,BLE 模式下也作为 SET_RD 查询的响应回。9240a3b 起 ALARM_SYNC_ADOPT 等价的 target diverge-then-adopt + echo-suppression(沿用 legacy lastUserTargetWriteAt 流程,记 CM4_ALARM_SYNC_ADOPT)已接通;40f3340(TAPD #1003050 ①)起 meat-type adopt 作为独立路径接通——_handleSettingsAscii 在 snapshot 的 meatTypeWireE 与 App 上次发送的 _DeviceState.cachedMeatTypeE 不一致、alarm enabled 且 2 s 用户写入窗口已过时,记 CM4_MEAT_SYNC_ADOPT 并通过 onProbeMeatTypeReconciled 回调把新 MeatType 同时写进 active CookingSession 与 pendingCookParamsProvider(cooking page 的 _meatType 通过对应 ref.listen 自动 resync);越界 E 不 adopt 而记 CM4_MEAT_SYNC_SKIP,防固件回归污染 session。两路 ADOPT 互相独立——用户在物理屏只改 meat 不改 temp、或反之,都能反向同步。仍未接:doneness 与 alarm 配置其余字段(860de52 / TAPD #1003167 起 MQTT 订阅 /CM4/<id>/sett 已接通——MqttService._handleSettingsAscii 与 BLE 路径同 Liang 闸、cloud-local gate state、出口三回调与 BLE 同套 provider body;App 自发的 SET_xx= echo 不解析成 CM4Message、静默 fall through,无需 prefix-filter。详见上方 §SETT== 设置帧 ✅ callout)。
ee6634c(TAPD #1003063)补 connect-time 触发:CM4 不像 legacy 周期 push 12B,SETT== 帧只在「用户按物理键改设置」或「App 显式 SET_RD」时到来——所以 8 秒 ON/OFF 复位(每根探针目标回到 50 °C / 122 °F 默认)后 App 没事件触发反向同步、dashboard 仍显示旧目标。修复在 BleDeviceService._finishConnect 末尾按 deviceId.boosterFamily.isCm4 gate 调一次 _transportFor(deviceId).queryStatus()(CM4 transport 的 queryStatus 是 SET_RD 命令),记 CM4_READ_SETTINGS_ON_CONNECT;中继盒回的 SETT== 走既有 _handleSettingsResponse → pendingCookParamsProvider 路径刷新 dashboard。Legacy 0x55AE 设置查询不重新启用——legacy 走 ~3 秒一帧的 unsolicited 12B 流就够(per Liang 2026-05-06 已下线 0x55AE 周期查询)。待 CM4 HW 验证:booster 是否在 connect 后立刻应答 SET_RD、是否 beep。
App 每 20 秒发 — Per Liang 2026-06-06 / TAPD #1003176 已澄清:60 秒是中继盒固件层 独占锁 TTL(与 legacy 0x55B1 同机制),App 每 45 秒发 CNT_0,注释说「不发的话约 60 秒后链路死掉」。但 60 秒是 BLE 连接监督超时还是 CM4 固件层的 idle disconnect?CNT_0 刷新锁;锁到期后中继盒主动 shed GATT 链路(c613148 起 cadence 由 20 s 改 45 s 跟 legacy 对齐)。
CM4 自己是否会主动断开空闲连接、有没有可配置的超时时间——unknown。
当前固件仅发 PENOFF <N>(N ∈ {1, 2, 3, 4}),无 PENON push(上线由 D=<hex> 遥测隐式表示)。App 侧 _parseProbeIdentifier 仅接受数字 1–4,遇到 legacy 颜色名会返回 null 丢弃。
USUS=2 = US2 还是 EU ✅ 已解决(cloud doc 2026-05-11 / 351430f)Per cloud doc 2026-05-11:USUS=2 是 EU broker(k211a27b.ala.eu-central-1.emqxsl.com:8883),与 App 的 MqttServerConfig.eu host 完全一致。Q6 原始矛盾(cm4_protocol.dart 旧 docstring 写 "US2"、MQTT 与云端 broker 表写 "EU")由 cloud doc 落定后者;旧 docstring 已重写,setServerRegion / ServerRegion / README 里 "pending cloud-doc" 的 hedge 全部去掉。完整 broker 映射(0=CN cn-hangzhou / 1=US us-east-1 / 2=EU eu-central-1,均 TLS 8883)见 MQTT 与云端 §区域 broker。
cm4_protocol.dart 列的是 App 当前发的命令——不是 CM4 固件支持的全集。Liang 可能在固件里实现了 timezone、language、factory reset、explicit alarm-state push 等,但 App 没接。下次 spec review 时一并问。
Topic 已知三个:/CM4/<deviceId>/data(中继盒 → App 遥测)、/CM4/<deviceId>/sett(中继盒 → App 推送:SETT== / WIFI_STS / MQTT_STS / <id> online / SET_RD 应答)、/CM4/<deviceId>/apps(028087a / V14 / Per Beta 2026-06-16 新增:App → 中继盒命令)。中继盒是否还订阅 / 发布其他 topic(status / OTA-progress / 离线遗嘱等)——unknown。
| 项 | v4.2(legacy) | CM4(本页) |
|---|---|---|
| 协议格式 | 二进制 0x55XX | ASCII |
| 命令通道 | AE03 | AE03 + MQTT |
| 数据通道 | AE05 indicate | AE05 notify + MQTT |
| 探针数 | 1–3 | 1–4 |
| 字节级权威 | v4.2 §一-四 | 本页 |
| 共享字节布局 | 15-byte 探针包、NTC 查表 | 同 v4.2 |
15-byte 探针包的字节级解析(NTC 查表、字节序、MAC 还原)只在 v4.2 描述。CM4 解码出 binary 后走同一逻辑——temperature_lookup.dart 是双家族共享文件。
lib/core/protocol/cm4_protocol.dart(208 行)— App 发出的所有命令常量与 builder;行 61-74 的 SET prefix 历史注释块是 flip-flop 时间线权威,改 prefix 前必读lib/core/protocol/cm4_data_parser.dart — 解析 D= / PENON / PENOFF / SETT== / WIFI_STS= / MQTT_STS= / M_ID OK / M_PD OK,以及 RI_CNT=N 回读、Ver=V<decimal>+MAC... 版本/MAC 应答、MQTT /sett 上的 <deviceId> online broker-login 公告lib/core/transport/cm4_booster_transport.dart — 命令派发;CD hex 编码后守卫从 > 99 放宽到 > 0xFF(a3c9cf7)lib/core/services/mqtt_service.dart(585 行)— MQTT 路径(topic、TLS、broker)lib/core/protocol/temperature_lookup.dart(199 行)— 与 legacy 共享的 NTC 查表