本章列出 App 里所有状态机及其编码位置。Agent 在实现新功能或改现有行为时,应该从这里找到「状态当前存在哪里、如何派生」而不是自己发明。
Per spec 2026-04-30 review item #3:状态模型按以下三层组织——产品形态(CM1/CM2/CM3/CM4)→ 中继盒级快照(每盒一份) → 探针级快照(每探针一份)。LostConnection 判定与扫描-连接判据作为独立 callout 在中继盒级章节里说清。
Liang 2026-04-27:补充产品形态,让代码 + 文档侧 agent 理解硬件结构。
| 型号 | 中继盒 | 探针数 | 探针颜色 | 旧名称 |
|---|---|---|---|---|
| CM1 | 1 | 1 | 黑 | MW4 改名 |
| CM2 | 1 | 2 | 黑 + 白 | MW5 改名 |
| CM3 | 1 | 3 | 黑 + 白 + 蓝 | — |
| CM4 | 1 | 最多 4 | 黑 + 白 + 蓝 + 黄(V5 spec 2026-05-14) | — |
| MW3 | 1 | 2(已停产) | — | 物理 2 探针 2nd-gen 中继盒(竹盒、无屏);同 CM1 协议族但 App getProbeCount 未特判(fallback 4)—— 见 booster.dart dcf2699 EOL 注释(Kevin 2026-05-29 确认 EOL,无开放 TAPD) |
每台中继盒内置蜂鸣器 + 显示屏 + 3 按键 + USB-C 充电口。中继盒级显示项:温度单位(°C/°F)、电量、充电状态、预报警标识状态。每根探针级显示项:内/外温、连接状态、电量。每根探针有独立的预报警目标温度,由用户在中继盒按键设定,蜂鸣器达到目标后响。
App 端含义:CM1 单探针场景,CM2/CM3/CM4 多探针场景。每根探针独立维护 12B / SETT== 解析字段(详见 §探针级 §探针 ProbeAlarmConfig 子节)。家族判别由 BoosterFamily { legacy, cm4 } 收敛(lib/core/transport/booster_family.dart)。
枚举:DeviceStatus(lib/core/models/booster.dart)
| 值 | 含义 |
|---|---|
connected |
正常接收数据 |
lostConnection |
中继盒无数据(已超出宽限期) |
boosterShuttingDown |
入仓后过渡态,UI 显示"正在关机..."持续 10s(_dockShutoffUiDelay) |
boosterOff |
入仓关机完成,中继盒电源已切断 |
allDocked |
中继盒报告所有探针入仓 |
设备快照:Booster(lib/core/models/booster.dart,310 行)
per spec 2026-04-30 review item #3:DeviceStatus 增加项 + 12B 解析得来的字段都已落到 Booster 快照上。下表是单一来源的字段清单,AI / 开发者改 UI 前从这里查:"是不是已经在 booster 里了"。
| 字段 | 类型 | 来源 | 含义 |
|---|---|---|---|
isBleConnected |
bool | BLE 状态机 | 原始连接状态(宽限期内仍为 true) |
deviceStatus |
enum | 派生 | 见上枚举 |
isInGracePeriod |
bool | 计时器 | 90 s 静默重连窗口活跃中 |
batteryLevel |
int (0–10) | legacy 12B byte 1 / 15B byte 1 / 2B byte 1 ; CM4 D=hex byte 1 / SETT== byte 0 | 中继盒电量(× 10 = %) |
isCharging |
bool | 同上字节高 nibble = 8 | 中继盒充电状态(0x80–0x8A 编码,per Liang 2026-04-28) |
firmwareVersion |
int | 同电量字节后一位 | 中继盒固件版本(V{十进制}) |
useCelsius |
bool | legacy 12B byte 3 ; CM4 SETT== byte 2 | 中继盒物理屏温度单位。自 ec99c9b 起 App 在 post-MUTE rearm 路径会消费此字段——legacy 0x55AF/B0/B2 SET_TARGET 的 byte-2 unit flag 不只是温度元数据,固件还把它当成中继盒全局显示单位采纳,所以 background re-arm 必须用 booster 自己当前的单位回写,否则会翻转屏显(TAPD 2026-05-19)。用户主动 Start 的 SET_TARGET 仍发 App 单位偏好——那是有意的单位切换。 |
wifiStatus |
enum | CM4 SETT== / WIFI_STS=N push |
CM4 only;0=disconnected / 1..4=bars |
mqttConnected |
bool? | CM4 MQTT_STS=N push(9f419a8 / Beta 2026-06-15 V13,10 s 一推) |
CM4 only;true = booster 自己 broker session 健康(Beta 指定的「配网成功」信号),false = broker 不可达,null 直到首推到达(或 non-CM4)。与 wifiStatus 的 AP join 状态区分——盒子可能 WiFi 满格却到不了 broker。BoosterNotifier.snapshotsEqual 已加该字段比较,防 false→true 翻转被去重吞、让 WiFi 配对 verdict poll 能命中 |
serverRegion |
enum | CM4 USUS=N 查询响应 |
0=China / 1=US / 2=EU |
backlightLevel |
int (1–3) | CM4 SETT== / BL_LVL=N 查询响应 |
中继盒物理屏亮度;可在物理按键改,SETT== push 反向同步 |
ringVolumeLevel |
int (1–3) | CM4 SETT== / RI_LVL=N 查询响应 |
中继盒报警音量;仅 App 可改(物理按键不能) |
ringCount |
int? (0–3) | dead 字段(7c141d7 / TAPD #1003203):原设计读 RI_CNT=? 应答,但 booster 不回报;live source 改为 buzzerRingModeProvider per-device SharedPreferences |
报警响铃时长档位(0=持续 / 1=3 声 / 2=1 分钟 / 3=5 分钟)。仅 App 可改——booster 接收并保存 RI_CNT=N 但不应答任何 RI_CNT 帧(含 RI_CNT=?),所以 _handleRingCount 永不触发、Booster.ringCount 永不更新;UI 直接读 buzzerRingModeProvider(StateNotifierProvider.family,SharedPreferences key buzzer_ring_mode_<deviceId>,默认 0=持续)即刻反映用户的 pick。先前 3be4358 把它误绑成 TAPD #1003138 后 c8d5ed5 整体回退;Liang 2026-06-09 澄清「RI_CNT 是独立响铃时长命令、不属于 #1003138;TAPD #1003138(ring/silent Mute 开关)是 per-probe SET_<color>=ABCDEF 第 1 位 A 字段(作为 Mute 选项叠加在同一 Buzzer 控件顶部),见 §探针配置」后 2f3e778 重新接 plumbing;read-back gap 由 7c141d7 改本地持久化兜底 |
rssi |
int? | BluetoothDevice.readRssi() 周期 10 s(ble_device_service.dart:3516) |
中继盒 BLE 链路 RSSI(dBm),首次采样前 / 断开后为 null。与 Probe.signalStrength 不同——后者是 15B byte 14 的每探针 RSSI |
preAlarmActive |
bool | derived = OR of ProbeAlarmConfig.isAlarmActive across probes |
中继盒预报警标识状态——任一探针 12B byte 6("用户启用中继盒报警"标识)为 1 时为 true |
connectedProbes |
List<Probe> |
多包累积 | 当前连接的探针列表,详见 §探针级 |
probesInDisplayOrder |
List<Probe> (derived) |
connectedProbes 排序视图 |
UI 探针列表顺序的单一来源——CM4 走 LCD 顶到底色序 blue/white/black/yellow(per Beta 2026-05-11,5b70fbd),legacy 走 ProbeNumber.number 自然序 black/white/blue/yellow;dashboard 探针卡片、ℹ︎ 信息对话框、设置页固件列表全部通过此 getter 取,避免漂移。Dev overlay 故意保持 raw slot 序便于调试。底层是新 ProbeNumber.cm4LcdOrder getter(probe3→1 / probe2→2 / probe1→3 / probe4→4) |
lastConnectedAt |
DateTime? | _finishConnect 时戳 |
重连后 60 s 内 alarm evaluator 暂停 probeDisconnected 评估(让残留 15B 静默自然刷新或被确认) |
⚠️ 12B byte 6 语义 per Liang 2026-04-30 audit reply:是 V3 静态语义「用户已启用该探针的中继盒报警」,不是「正在响铃」。用户在中继盒物理按键 mute = disable 该配置(字节回 0)。这是配置状态,不是瞬时蜂鸣状态。
ProbeAlarmConfig.isAlarmActiveApp-side 字段名沿用旧拼写,doc-comment 已更正。
中继盒判为 lostConnection 的条件——三类包同时沉默才算:
连续 1 分钟 & 没有 15B & 没有 12B & 没有 2B
实现层细节:单一 _DeviceState.lastBoosterData 时间戳被三种包都重置(legacy:15B at _handleDataPacket、12B at _handleSettingsResponse、2B at _handleBoosterHeartbeat、0x55B0 at _handleProbeStatusNotify;CM4:D=hex at 同等位置 + SETT== / PENON / PENOFF 同样刷新)。_checkStaleness 每 3 s 评估,now - lastBoosterData > 60 s → DeviceStatus.lostConnection(V4 时阈值是 15 s,2026-04-30 audit 提高到 60 s,ble_device_service.dart:3649-3656)。
判定中继盒能否建立连接——广播任一类包即可:
发现任意 15B | 12B | 2B(legacy)或
D=/PENON/SETT==(CM4)广播都可以与设备建立连接
包括 lostConnection 之后:App 在扫描里发现已保存设备只广播任意一类,立即与它建立连接,不需要等多类齐全。BLE 层用设备名前缀(CM* / MW* / CG*)匹配,连接后任意 RX 包都重置 lastBoosterData → 状态翻回 connected。
每根探针有 3 个独立快照,按 (deviceId, ProbeNumber) 索引(CM1 一组、CM2 两组、CM3 三组、CM4 最多四组):
Probe(telemetry — 15B / D=hex 解析) — 探针硬件读数ProbeAlarmConfig(中继盒侧 — 12B / SETT== 解析) — 中继盒按键设的目标 + 启用标识ProbeSensorFlags(App 侧派生) — 4 个 ADC 范围标志下面三节分别说。
Probe(15B / D=hex 解析)per spec 2026-04-30 review item #2 + 固定代码:探针应该从 15B 数据包(CM4 走 D= ASCII 解码后同一布局)解析以下 6 个字段。probe.dart(378 行)的 6 字段 telemetry 形态是 固定代码——加减字段前必须重新跟 Liang 确认。
| 字段 | 类型 | 来源 | 含义 |
|---|---|---|---|
internalTemp |
double? |
15B byte 3-4 (大端 ADC) → tempIntArray | 探针内温 °C;低于 LUT 范围 = null(UI 显 LO);> 101 °C 触发 HI 状态,120 °C 是 parser 硬钳位(详见 BLE 协议 §温度查表) |
ambientTemp |
double? |
15B byte 5-6 (大端 ADC) → tempExtArray | 探针外温 °C;< 40 °C UI 显 ---;≥ 275 °C 钳位 + 全屏报警 |
batteryLevel |
int (0–10) |
15B byte 7 | 探针电量(× 10 = %) |
firmwareVersion |
int |
15B byte 8 | 探针固件版本(V{十进制},例 0x88 → V136) |
rawAddressBytes |
List<int> |
15B byte 9-14 (线上反向顺序) | 探针 MAC(线上字节);address 字段是反转后的 canonical hex string |
signalStrength |
int (dBm) |
15B byte 15 (-(0x100 - raw)) |
探针信号强度(per-probe,与 Booster.rssi 不同) |
App 侧派生的连接 / 入仓字段(不在 15B payload 里,由状态机维护):
| 字段 | 类型 | 含义 |
|---|---|---|
isConnected |
bool |
探针向中继盒发数据(20 s 15B 沉默 → flip 为 false,触发 PROBE_DROPOUT 事件 + UI 置灰) |
isDocked |
bool |
物理入仓(收到 0x55AA / PENOFF) |
lastSeenAt |
DateTime |
最后一次 15B 时间戳(探针断连 60 s 报警评估的时间基准) |
探针断连判断(per spec 2026-04-30 已固定代码):
Probe.isConnected = false,UI 探针卡片置灰,保留 lastSeenAtAlarmType.probeDisconnected(UI 弹窗 + 通知栏 + 铃声 + App 角标 + 锁屏文字),文案"探针未连接中继盒。请移动中继盒,使它靠近探针"Per spec 2026-04-24 review:
Probe只承载硬件 telemetry。2026-04 重构分两步把非 telemetry 字段都迁出:
- 烹饪参数(
meatType/doneness/targetTemp) → 活跃会话由CookingSession持有、会话前的草稿态由pendingCookParamsProvider持有、App 写过的 target 缓存到 SharedPreferences(ds.cachedTargets;BleDeviceService.onProbeTargetCacheHydrated回调在连上时灌回pendingCookParamsProvider)。详见 §烹饪会话。- ADC 范围标志 + 中继盒侧目标/报警状态 → 详见下面两个独立快照。
highTempAlarm/lowTempAlarm是无读者的死字段,重构里直接删除。
⚠️ 固定代码(per spec 2026-04-30 [确认A][确认A]):状态模型 wiki 重写时再次跟 Liang 对了边界——上面两条都是 locked。(1) 烹饪参数继续按
CookingSession/pendingCookParamsProvider/cachedTargets三处分裂,不要回塞到Probe。(2)ProbeSensorFlags+ProbeAlarmConfig继续住在alarm_service.dartfamily providers,不要折回Probe数据类。代码里对应这三个类的 doc-comment 已加⚠️ FIXED (固定代码)标注,未来 agent 单看文件能看到 placement 是 Liang 批的、不是历史漂移;要动它们之前先回到 Liang 那里重新确认。
ProbeAlarmConfig(legacy 12B 解析)每条 legacy 12B 设置响应都是 per-probe 的(字节 9-12 是 MAC,标识当前条对应哪根探针)。CM1 的 12B 周期里只有一条;CM2 两条(黑、白各一);CM3 三条。ProbeAlarmConfig 仅用于 legacy 12B 的每探针配置;CM4 不在 wire 上携带这类状态,cooking target 住在 App 侧 CookingSession,因此该 provider 对 CM4 保持为空。
数据类:ProbeAlarmConfig(lib/core/services/alarm_service.dart:1090,与 AlarmService + ProbeSensorFlags 同模块——按 Liang 2026-04-24「alarm 状态放 alarm 模块」指示)
| 字段 | 类型 | 来源 | 含义 |
|---|---|---|---|
targetTempC |
int? |
legacy 12B byte 4-5 (LE16 ADC) → tempIntArray 反查;CM4 SETT== byte 3-4 (LE16) | 已废弃 (V5 2026-05-07 abandoned)。原 App 侧 °C 目标,因固件量化丢失精度已弃用,改用 targetTempFRef / targetTempCRef |
targetAdcRaw |
int? |
同上 byte 4-5/3-4 (LE16, raw) | 原始 ADC HEX 值(例 0x0178),诊断对账用 |
targetTempFRef |
int? |
byte 7 / SETT== byte 6 | 中继盒预转换的 °F 目标(°F 显示模式下的权威值,承载用户原始输入) |
targetTempCRef |
int? |
byte 8 / SETT== byte 7 | 中继盒预转换的 °C 目标(°C 显示模式下的权威值,承载用户原始输入) |
isAlarmActive |
bool |
byte 6 / SETT== byte 5 | V3 静态语义「用户已启用该探针的中继盒报警」(per Liang 2026-04-30 audit)。用户在中继盒物理按键 mute = disable 该配置(字节回 0)。不是瞬时响铃状态。⚠️ 065d153 / TAPD #1003138 曾把 CM4 SETT== byte 5 扩为 3 值(0=关 / 1=响铃 / 2=静音仅背光)并新加 CM4SettingsAscii.silent 承接 ring/silent 拆分,但 738fdeb / TAPD #1003233(Beta 2026-06-11)整体回退——固件实证只接受 A∈{0,1}、任何 A=2 让整条 SET_<color>= 被丢弃(SET_BU=214F25 无 SETT== ack 也无 LCD 更新),Mute 自此纯走 booster-level RI_CNT=4;CM4SettingsAscii.silent 字段 + Cm4DataParser 的 silent 解码与 bytes[5] == 0x02 分支一并删除。parser 仍保留 bytes[5] != 0x00(而非 == 0x01)作为防御性 fallback——老固件若回未知值仍读作"armed"而不致被 #1003117 ack 检测误读为 device-MUTE 1→0 edge。Legacy 12B byte 6 仍二值 |
Provider:probeAlarmConfigProvider((deviceId, ProbeNumber)) family(alarm_service.dart:1192),与 probeSensorFlagsProvider 同住。
对齐产品形态(Liang 04-27 + 04-30):
| 型号 | 12B/SETT== 周期内条数 | ProbeAlarmConfig 实例数 |
|---|---|---|
| CM1 | 1(黑) | 1 |
| CM2 | 2(黑、白各 1) | 2 |
| CM3 | 3(黑、白、蓝各 1) | 3 |
| CM4 | 至多 4(按当前在线探针) | 至多 4 |
派生:Booster.preAlarmActive = OR over connectedProbes of probeAlarmConfigProvider(...).isAlarmActive,在 _emitBoosterUpdate 里每次 emit 重算。
ALARM_SYNC_ADOPT gate(per Liang 2026-04-30 audit):当 12B 里的 targetTempC 与 App 侧 cachedTargets 有差异 且 isAlarmActive == true 时,App 才把 booster 侧目标 adopt 进 cookingSession。两个条件同时满足才表示「用户在中继盒按键改了目标」的明确意图。CM4 端的 SETT== 等价 ALARM_SYNC_ADOPT 已分两路接入:9240a3b 接通 target adopt(CM4_ALARM_SYNC_ADOPT,复用 2 s lastUserTargetWriteAt 窗口抗 echo),40f3340 接通 meat-type adopt(CM4_MEAT_SYNC_ADOPT,TAPD #1003050 ①,per-probe _DeviceState.cachedMeatTypeE 记录 App 上次发送的 E 值、共享同一 2 s 用户写入窗口);doneness 与 alarm 配置其余字段仍 log 但不 adopt,详见 CM4 协议 §Q3。
ProbeSensorFlags(App 侧派生)Probe 重构时把 4 个 ADC-out-of-range 标志移出,按「alarm 状态放 alarm 模块」指示落到 alarm_service.dart:
| 字段 | 含义 |
|---|---|
isInternalTempBelowRange |
内温 ADC < LUT 最低 → UI 显 LO |
isInternalTempAboveRange |
内温 > 101 °C (per Liang 04-29 v5 §15)→ UI 显 HI + 全屏报警 |
isAmbientTempBelowRange |
外温 ADC < LUT 最低 → UI 显 --- |
isAmbientTempAboveRange |
外温 ≥ 275 °C → UI 钳到 275 °C + 全屏报警 |
Provider:probeSensorFlagsProvider((deviceId, ProbeNumber)) family。Bridge:BleDeviceService.onProbeSensorFlagsUpdated + MqttService.onProbeSensorFlagsUpdated 每包刷新。读者:alarm 评估器、cooking 页 LO/HI 渲染、dashboard chip、warning 页。
ConnectionDisplayState枚举:ConnectionDisplayState(lib/core/models/connection_display_state.dart,122 行)
| 值 | 触发条件 |
|---|---|
online |
探针活跃,数据流通 |
docked |
物理入仓(压倒大部分其他状态) |
recentlyLost |
60s 宽限期内(graceActive=true) |
disconnected |
宽限期结束,UI 显示 Disconnected/已断开,并提供重新扫描入口 |
reconnecting |
用户主动触发的重连(带 spinner,UX 不同) |
派生函数:connectionDisplayStateFor(...)(探针级)+ boosterConnectionDisplayState(...)(中继盒级),都以 graceActive 作为 BLE 宽限期信号;714a1e3 / TAPD #1003266 / Per Liang 2026-06-17 起两个 helper 都新增 cloudLive 入参——cloudLive == true 时设备直接视为 online(仅 docked 胜过),WiFi 模式下中继盒 BLE-locked 但 stream over cloud 时的瞬时 BLE drop/reconnect 不再渲染为 disconnect。
UI 层只读这个投影,不直接看 Booster.isBleConnected 或 Probe.isConnected。原因:投影把宽限期、手动重连、入仓等 edge case 已经归一化。
CookingSession数据类:CookingSession(lib/core/models/cooking_session.dart,323 行)
状态枚举:CookSessionStatus
active:进行中completed:用户正常结束cancelled:用户取消disconnected:连接中途丢失会话字段:
deviceId, probeNumber —— 哪台设备的哪根探针meatType, doneness, targetTemp —— 烹饪参数startTime, endTime —— 时间戳history[] —— TemperatureReading(timestamp, internalTemp, ambientTemp) 列表peakTempF, notes —— 摘要字段earlyWarnLevel —— per-session 早期警告档位(per Liang TAPD #1003091 / 33b4e62,alarm evaluator 不再读全局 earlyWarnLevelProvider)Notifier:CookingSessionsNotifier(device_providers.dart)
⚠️ Per Liang 2026-06-02 / TAPD #1003117 Layer B(62621923):会话完成判定换 4-条件状态机。达到目标温度不再立即结束 cook——alarm evaluator 改调 markTargetReached 锁住「目标到达」时间节点(targetReachedTime)+ 把 crossing 帧折进 peak 与 history、并 arm 一个 20 min 未确认 trailing 录制窗,cook 继续录制直到下列 4 个 OR 条件任一触发:① 未 acknowledge → 20 min 超时;② 通知 tap acknowledge → min(ack+5min, target+20min)(NotificationService.onAlarmTapped → acknowledgeTargetReached(source: 'notification_tap'));③ 中继盒 MUTE acknowledge → 同样 min(ack+5min, target+20min)(alarmEnabled 1→0 边沿 → source: 'device_mute';CM4 物理 MUTE 走同一 edge,booster MUTE 默认 ack 该盒上每根 post-target 探针);③′ App 内 banner 「确认」tap → 直接 acknowledgeTargetReached(source: 'in_app_confirm')(e6045c7 → 682c66b / TAPD #1003197,per Liang 2026-06-09「点了确认才算停止」)——原本 in-app 「确认」只发 muteAlarm + 清 banner,靠 ③ 的 alarmEnabled 1→0 echo 来 ack,legacy 走得通但 CM4 的 alarm flag 在 D=<hex> 里、edge detector 看不到,整段窗 5 min 等满才 finalize;e6045c7 让 _AlarmCardOverlay 在 dismiss targetReached 时直接调 acknowledgeTargetReached,682c66b 再扩到 internalOverTemp / ambientOverTemp dismiss——100 °C over-temp 阈值的探针必然已 past target,先前 over-temp Confirm 不停止 timer 即是症状根因。acknowledgeTargetReached 自身 gated 在 active post-target unacked session 上,对未达 target 的探针自动 no-op(保留 Liang partial-MUTE 规则:仅达标探针 finalize、其余继续 cook),与 booster-MUTE echo 幂等(first ack wins)。muteAlarm(0x55AD)仍只在 targetReached dismiss 时发——over-temp 是 App-side 推导、中继盒侧无报警可静音;④ App 手动关闭 → 仅对 post-target cook 立即 finalize、pre-target cook 留作下次启动 resume(_AppExit.run → finalizeActiveForAppExit)。Per Liang 2026-06-08(8cf4a39 / TAPD #1003117 sub-correction):20 min 是硬上限——late ack(如 minute 19 才 tap)完成时间落在 minute 20、不会再加 5 min;acknowledge 只能缩短 trailing window,不能延长。具体边界由 CookingSession.ackWindowEnd(ackTime, ackTail, hardCap) 返回 min(ack+ackTail, target+hardCap) 计算,acknowledgeTargetReached 按 remaining gap 调 timer。同探针在窗内重 START 把旧 cook 在 START 瞬间 finalize(条件 1B/2B/3B),硬杀(无 callback)→ 重启时按最后落盘样本「记到哪算哪」补完成时间——为此 active session 的 history 现持久化(不再 strip,per-sample 写入按 20 s 节流),新增字段 targetReachedTime / acknowledgedTime 也随 JSON round-trip。Model 拆出 markTargetReached / finalizeComplete 两个方法,旧 finalizeOnTargetReached 保留为 Layer-A regression 守卫;cook-log 时间轴因此固定有三个节点(早期警告 amber / target-reached green / session-complete blue),其中 session-complete 蓝点钉在 endTime 处,与 green target 不再重合,详见 烹饪日志详情 §X 轴 / 时间标签 + 节点。
AlarmService 的优先级栈文件:lib/core/services/alarm_service.dart(1194 行;末尾追加 ProbeSensorFlags + probeSensorFlagsProvider、ProbeAlarmConfig + probeAlarmConfigProvider,per spec 2026-04-24 + 2026-04-30 audit;两个类均带 ⚠️ FIXED (固定代码) doc-comment 标注;AlarmType 从 7 类经 2026-04-30 #18 扩到 12 类,2026-05 再加 ambientUnderTemp 共 13 类,详见 告警与通知 §报警类型与优先级)
状态(Map 键为 (deviceId, ProbeNumber?) 2-tuple,TAPD 2026-05-11 多探针回归后由 deviceId 单键升级;全局排序标识 AlarmKey 才是 3-tuple):
_active[(deviceId, probe)]:当前正活跃的报警类型集合(条件仍为 true)_userDismissed[(deviceId, probe)]:用户已 dismiss 但条件仍 true 的报警类型集合topAlarm:所有设备中优先级最高的未被 dismiss 的报警 —— Warning 页只看这个AlarmType 13 类(值 = 优先级,越小越高):
| 值 | 名称 | 条件 |
|---|---|---|
| 0 | ambientOverTemp |
环境温 > 275 °C / 527 °F |
| 1 | internalOverTemp |
内温 ≥ 100 °C / 212 °F(per Liang 2026-05-07 ≥ 不是 >) |
| 2 | probeDisconnected |
12B 仍在但 15B 沉默 60 秒(探针 ↔ 中继盒断) |
| 3 | targetReached |
达到目标温度 |
| 4 | earlyWarning2 |
目标前 5 °C / 9 °F |
| 5 | earlyWarning1 |
目标前 10 °C / 18 °F |
| 6 | lowBattery |
探针 ≤ 20%;≤10% 进入 entry 再响一次后不再 repeat(per Liang TAPD 2026-05-15) |
| 7 | boosterDisconnected |
DeviceStatus.lostConnection(60 秒 12B/15B/2B 全沉默) |
| 8 | boosterPoweredOff |
boosterOff / boosterShuttingDown 进入瞬间 |
| 9 | probeDocked |
Probe.isDocked == true |
| 10 | boosterLowBattery |
中继盒 firmwareVersion > 0 且 batteryLevel ≤ 2,>3 滞回(固件门防 fresh-connect 假报);≤10% 进入 critical tier,每 10 分钟重复一次(per Liang TAPD #1003012 2026-05-12) |
| 11 | internalUnderTemp |
内温 < 32 °F(= 0 °C) |
| 12 | ambientUnderTemp |
外温 < 104 °F(= 40 °C) |
声音:
ambientOverTemp / internalOverTemp / targetReached)震动:触发时 heavy impact
评估位置:alarmStateProvider(device_providers.dart:1750–1971)——之前部分散在 cooking_page.dart 的 telemetry tick,2026-05 已统一搬到 provider body。重评由 main.dart 两条路径驱动:
container.listen(connectedBoostersProvider, ... container.refresh(alarmStateProvider)) —— 新遥测触发同步重评Timer.periodic(5s, ... container.refresh(alarmStateProvider)) —— 兜底 no-telemetry 场景(probe-disconnect 阈值不会自然 emit)boosterProvider.family、probeByNumberProvider、cookingSessionsProvider(per-session earlyWarnLevel,per Liang TAPD #1003091 / 33b4e62)ref.read(alarmStateProvider.notifier).trigger/clear 驱动alarmStateProvider 的生命周期:在 _AppInitializer.build() 里 ref.watch keep-alive,跨所有页面。这样无论用户在哪个屏幕,topAlarm 变更都会触发 Warning 全屏弹出(由 app.dart:63-90 的监听器负责导航,仅对 internalOverTemp / ambientOverTemp 两类 safety alarm 全屏,其它走顶部 AlarmBannerHost)。
HI 钳位与 WarningPage 的关系(per Liang 2026-05-01 v5 review):
internalHiClampC = 101 —— UI 显示 HI 的阈值internalHardClampC = 120 —— parser 硬钳上限AlarmThresholds.internalOverTempF = 212(= 100 °C)是 alarm 触发阈值——alarm 在 HI 钳位之前 1 度就开始响关键回调:WidgetsBindingObserver.didChangeAppLifecycleState(state)
状态转换点:
inactive / paused / resumed —— 标准 Flutter 生命周期detached —— App 即将被系统 kill(iOS + 部分 Android)。触发 Layer A 退出钩子:_AppExit.run() 里 await BleDeviceService.shutdown()(2s 超时)+ MqttService.shutdown()(1s 超时)+ CookingLiveActivityService.endAll()(500ms 超时)Android 额外层(Layer B):MethodChannel culinatech.app/lifecycle
onDestroy → Dart side 收到 'onDestroy' call → 触发同一个 _AppExit.run()onTaskRemoved(用户从最近任务列表划掉)→ 当前未实现(需要 Service 子类,见 待解决问题 TAPD #9)后台 4 小时后切换重连节奏:
BleDeviceService._bgTieredCadenceArmTimer + _inBgTieredCadenceMode 实现(修订自 2026-04-28 的"被动模式" _powerSaver* 读法)。详见 平台集成 §4 小时后台空闲降级。| Provider | 文件 | 语义 |
|---|---|---|
boosterProvider.family |
device_providers.dart |
以 deviceId 为键,每台设备独立快照 |
connectedBoostersProvider |
同上 | 当前已连接的设备列表 |
probeByNumberProvider |
同上 | 以 (deviceId, probeNumber) 为键 |
connectionStateProvider |
同上 | App 级连接总览 |
alarmStateProvider |
同上 | 全局报警栈(AlarmService wrapping),评估器主体(1750–1971) |
cookingSessionsProvider |
同上 | 活跃 + 近期会话 |
cookLogProvider |
同上 | 持久化历史(cap 50 条) |
temperatureUnitProvider |
同上 | 摄氏/华氏切换 |
appLanguageProvider |
同上 | App 显示语言(6 种) |
lastBoosterNameProvider |
同上 | 最后一次使用的设备名(给 UI 做提示) |
earlyWarnLevelProvider |
同上 | 报警前置距离(±5 °C / ±10 °C)的 UI 选择 seed(meat-presets picker、cooking 页本地状态种子);自 33b4e62 起 alarm evaluator 改读 per-session CookingSession.earlyWarnLevel,不再直接消费此 provider |
useMockServiceProvider |
同上 | Mock vs Real BLE 服务切换(session-only,不持久化) |
probeSensorFlagsProvider.family |
alarm_service.dart |
4 个 ADC 范围标志(每探针) |
probeAlarmConfigProvider.family |
同上 | 中继盒侧目标 + 启用标识(每探针) |
cookingLiveActivitySyncProvider |
cooking_live_activity_service.dart |
iOS Live Activity start/update/end 同步(Android no-op) |
Booster.isBleConnected,走 connectionDisplayStateFor / boosterConnectionDisplayState 派生(deviceId, probeNumber) 为路由参数)