CM4 专属的 OTA 固件升级页(TAPD #1003304),从 Booster Configurations 齿轮页的 "Firmware Update" 行进入。进页先查中继盒当前 ESP(乐鑫 WiFi 芯片)固件版本,再查服务器最新版本号,两者整数对比后分「已是最新 / 有新版本」两态;用户确认升级后发 OTA 命令,中继盒自行重启进入 OTA 模式、通过 WiFi 从固定的阿里云 OSS 地址下载并自装,App 只负责读版本决定要不要提供升级入口,以及监听 OTA=N% 推送渲染真实进度条、装完后轮询版本号确认成功(TAPD #1003333,per Liang 2026-07-03 复测反馈:先前流程升级中会卡死在"正在升级…"、也没有真正的完成信号)。
lib/features/thermometer/presentation/pages/firmware_upgrade_page.dart(480 行)connectedBoostersProvider(读 espFirmwareVersion)、bleDeviceServiceProvider(发 VER / OTA 命令)、otaProgressProvider(StateProvider.family<int?, String>,TAPD #1003304/#1003333)deviceId(路由 arguments,裸字符串,与其它 per-device 路由一致)⚠️ espFirmwareVersion 与 Booster.firmwareVersion 是两个不同的字段,不要混用:firmwareVersion 是遥测字节 1 携带的 JieLi BLE 芯片版本,与本页无关;本页必须使用 espFirmwareVersion——由 VER 查询的回复(CM4VersionInfo)在 ble_device_service.dart:4210 的 _handleVersionInfo(BLE 路径)与 mqtt_service.dart:684(cloud 路径)两处填充,两条路径写的是同一个字段。Per Liang(TAPD #1003333):VER 读的是 ESP 版本,JieLi 芯片走独立的、不经本页的升级通道。
/firmware-upgrade(FirmwareUpgradePage.routeName)lib/app.dart 的 onGenerateRoute——因为需要 deviceId arguments,不在静态 routes mapBoosterConfigurationsPage(lib/features/thermometer/presentation/pages/booster_configurations_page.dart)"Firmware Update" 配置行,Navigator.pushNamed(context, FirmwareUpgradePage.routeName, arguments: deviceId)。与上方 Connection mode / WiFi network 两项不同,这一行没有 enabled: bleLinked 限制;_ConfigTile 默认 enabled = true,因此当前不是 BLE 直连时也仍可进入本页。单一 Scaffold + 简洁 AppBar(标题 l10n.boosterConfigFirmwareTitle),正文按当前阶段(_Phase 枚举:checking / upToDate / updateAvailable / upgrading / verifying / success / error)切换居中卡片:
CircularProgressIndicator + 说明文字("正在检测…" / "正在验证…")LinearProgressIndicator(percent == null 时为不确定态跑马灯)+ 说明文字check_circle_outline + 标题 + 当前版本号副标题(格式 V<n>)+ 单个"返回"按钮system_update_alt + 标题 + 副标题同时显示当前版本与最新版本 + 主按钮"立即升级"、次按钮"稍后"error_outline + 标题 + 主按钮"重试"(重新跑一遍 _check)、次按钮"返回"补充:服务器版本查询不是无限等待。_fetchLatestServerVersion() 只对连接建立(HttpClient.connectionTimeout)和等待 req.close() 返回响应这一步分别设置了 8 秒超时;后续读取响应体 utf8.decodeStream(resp) 本身没有再单独包一层超时。请求异常会进入 error,而非 200 响应或正文无法解析为整数时会返回 null,随后按“已是最新”分支处理。
initState 首帧回调,无需用户操作_check()
→ bleDeviceServiceProvider.sendBoosterSettingTo(deviceId, CM4Command.queryVersion) // 发 VER,BLE-or-cloud 路由
→ 轮询 _readCurrentVersion()(读 connectedBoostersProvider 里该设备的 espFirmwareVersion),
每 500ms 一次,最多 6 次(合计 3 秒)等回复落地
→ 若 3 秒后仍是 0(从未上报)→ phase = error
→ 否则 _fetchLatestServerVersion():HTTPS GET 固定地址
https://foneric-ota.oss-cn-shenzhen.aliyuncs.com/cm4/VER.txt(Beta 2026-06-26)
—— 纯文本十进制整数,与 espFirmwareVersion 同一刻度,直接整数比较
→ 网络请求失败 → phase = error;成功但 latest <= current 或解析失败 → phase = upToDate;
latest > current → phase = updateAvailable
_Message.onPrimary → _startUpgrade()_startUpgrade()
→ otaProgressProvider(deviceId).state = null // 清掉上次升级残留的进度
→ phase = upgrading
→ _armStallTimer() // 3 分钟无推送即判定失败
→ bleDeviceServiceProvider.sendBoosterSettingTo(deviceId, CM4Command.otaMode) // 发 OTA
→ SnackBar 提示已开始升级
OTA 后自行重启进入 OTA 模式,从固件里写死的阿里云 OSS 地址下载并自装新固件;App 不参与传输,只监听进度OTA=N%)ref.listen(otaProgressProvider(deviceId)) 监听到新值,build() 里挂OTA=N%(BLE 或 MQTT 任一通道,两条路径共写同一个 provider)都会重新 arm 3 分钟停滞看门狗;收到 100 或更大值时触发 _onInstallComplete()percent == 100 → phase 切到 verifying,看门狗取消,改起 2 分钟验证轮询(每 3 秒发一次 VER,直到读到的 espFirmwareVersion 大于升级前版本、或等于服务器最新版本号);2 分钟内未验证成功 → phase = error_Message.onPrimary → _checkNavigator.maybePop(context)OTA 命令——中继盒仍会继续自装。但页面 dispose() 时会取消本地的 _stallTimer 与 _verifyTimer。watch otaProgressProvider(deviceId) 驱动升级中的进度条;_phase 是本地 setState 管理的枚举,不经 Provider。两个计时器(_stallTimer 停滞看门狗、_verifyTimer 验证轮询)都在 dispose() 里显式 cancel(),避免用户中途退出后计时器继续跑并在已销毁的 State 上调用 setState。
无——本页纯协议驱动(VER / OTA / OTA=N% 均走既有 BLE-or-cloud transport 抽象),iOS/Android 共用同一套逻辑,无平台分支。
OTA 后只能死等,没有进度、没有完成判定,用户只能手动退出、重启 App、自行确认版本号。本次实现(停滞看门狗 + 真实进度条 + 重启后验证轮询)就是该反馈的修复。_otaVersionUrl 硬编码指向阿里云深圳 OSS,不随 RegionService 的 broker 区域(Asia/US/EU/HK)切换。若未来固件按区域分渠道发布,这里需要跟着改。v == _latestVersion 是兜底分支:正常路径是新版本号大于升级前版本(v > _currentVersion);等于服务器最新版本号这条兜底理论上覆盖"升级前版本读取有误差"的情况,但也意味着如果服务器版本号发生非升级性质的变化(如回滚),这里不会额外校验。