import 'dart:async'; import 'package:device_plugin_interface/device_plugin_interface.dart'; import '../../core/utils/logger.dart'; import 'eaicar_device_plugin.dart'; import 'eaicar_link.dart'; /// EaiCar(杰理方案)的 OTA 端口。 /// /// 推送本身在原生 `EaiCarOta`(从 lumi `PbMangerImp` 搬:`cmdOTAData` + 0xfc 后 /// 扫描回连 + 认证 + 续传),这里只做三件事: /// 1. 把原生事件翻译成 [DeviceOtaProgress]; /// 2. 终态锁定——成功后迟到的 failed/cancelled 不能把它覆盖回去(恒玄踩过); /// 3. [close]:升级成功设备会重启,退出页面时延时回连记住的设备。 /// /// 与恒玄不同,**这条链路不用断 app 层连接**:OTA 数据走同一条 GATT 的 AE01, /// 私有通道 ABC1 由原生在升级期间挡掉。stage2 阶段设备会主动断开重启, /// 那次「断开」是升级流程的一部分,不是失败——由原生判定,这里不看链路状态。 /// /// ⚠️ 上层 `DeviceOtaController` 的看门狗 90s 没进度就判失败,而 stage2 等待可达 3 分钟, /// 原生在这段窗口每 15s 发一次 `stage2` 心跳事件喂它。 class EaiCarOtaPort implements DeviceOtaPort { EaiCarOtaPort(this._link, this._plugin); static const String _tag = 'EaiCarOtaPort'; final EaiCarLink _link; final EaiCarDevicePlugin _plugin; final StreamController _progress = StreamController.broadcast(); StreamSubscription>? _sub; bool _running = false; bool _terminal = false; bool _succeeded = false; int _lastPercent = -1; @override bool get isRunning => _running; @override Stream get progressStream => _progress.stream; @override Future start(DeviceOtaRequest request) async { if (_running) throw DeviceException(DeviceErrorCode.otaBusy); if (request is! DeviceOtaFileRequest) { throw DeviceException( DeviceErrorCode.notSupported, '${request.runtimeType}'); } if (!_link.isConnected) { throw DeviceException(DeviceErrorCode.noActiveSession, '链路未就绪'); } _running = true; _terminal = false; _succeeded = false; // 升级中设备会断开/重启进 stage2,这期间插件的断线自动回连必须让路 _plugin.otaActive = true; _lastPercent = -1; _sub ??= _link.otaEvents.listen(_onEvent, onError: (Object e) { _fail(DeviceErrorCode.otaTransferFailed, '$e'); }); _emit(DeviceOtaState.inquiring, 0); try { await _link.otaStart(request.filePath); } catch (e) { _fail(DeviceErrorCode.otaTransferFailed, '$e'); rethrow; } } void _onEvent(Map e) { if (_terminal) return; final state = '${e['state']}'; final percent = (e['percent'] as num?)?.toInt() ?? -1; switch (state) { case 'preparing': _emit(DeviceOtaState.entering, 0); break; case 'transferring': // 只在单调递增时上报,SDK 的 preparing/prepared 会把百分比打回 0 if (percent > _lastPercent) { _lastPercent = percent; _emit(DeviceOtaState.transferring, percent); } break; case 'stage2': // 设备断开重启进 stage2,原生在扫描回连;进度条停在 15% 但要持续有事件喂看门狗 Logger.i(_tag, 'stage2 等待回连: ${e['message'] ?? ''}'); _emit(DeviceOtaState.rebooting, _lastPercent < 15 ? 15 : _lastPercent); break; case 'done': _terminal = true; _succeeded = true; _running = false; _emit(DeviceOtaState.done, 100); break; case 'cancelled': _terminal = true; _running = false; _emit(DeviceOtaState.cancelled, -1); break; case 'failed': _fail(DeviceErrorCode.otaTransferFailed, '${e['message'] ?? ''} (code=${e['code']})'); break; } } void _fail(String code, String message) { if (_terminal) return; _terminal = true; _running = false; _emit(DeviceOtaState.failed, -1, errorCode: code, errorMessage: message); } @override Future cancel() async { if (!_running) return; await _link.otaCancel(); // 原生会回 cancelled;5s 内没回就自己收尾(契约要求) Future.delayed(const Duration(seconds: 5), () { if (!_terminal) { _terminal = true; _running = false; _emit(DeviceOtaState.cancelled, -1); } }); } @override Future close() async { if (_running) await cancel(); await _sub?.cancel(); _sub = null; // 升级成功设备会重启,广播回来要几秒;失败/取消时原生已把链路断开,一样回连 final delay = _succeeded ? const Duration(seconds: 8) : const Duration(seconds: 2); Future.delayed(delay, () async { _plugin.otaActive = false; try { await _plugin.tryAutoConnect(); } catch (e) { Logger.w(_tag, 'OTA 后回连失败: $e'); } }); } void _emit(DeviceOtaState s, int percent, {String? errorCode, String? errorMessage}) { if (_progress.isClosed) return; _progress.add(DeviceOtaProgress( state: s, sentBytes: 0, totalBytes: 0, percent: percent, tsMs: DateTime.now().millisecondsSinceEpoch, errorCode: errorCode, errorMessage: errorMessage, )); } }