You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
5.2 KiB
159 lines
5.2 KiB
import 'dart:async';
|
|
|
|
import 'package:device_plugin_interface/device_plugin_interface.dart';
|
|
|
|
import '../../core/utils/logger.dart';
|
|
import 'smartcar_device_plugin.dart';
|
|
import 'smartcar_link.dart';
|
|
|
|
/// Smartcar(杰理方案)的 OTA 端口。
|
|
///
|
|
/// 推送本身在原生 `SmartcarOta`(从 lumi `PbMangerImp` 搬:`cmdOTAData` + 0xfc 后
|
|
/// 扫描回连 + 认证 + 续传),这里只做三件事:
|
|
/// 1. 把原生事件翻译成 [DeviceOtaProgress];
|
|
/// 2. 终态锁定——成功后迟到的 failed/cancelled 不能把它覆盖回去(恒玄踩过);
|
|
/// 3. [close]:升级成功设备会重启,退出页面时延时回连记住的设备。
|
|
///
|
|
/// 与恒玄不同,**这条链路不用断 app 层连接**:OTA 数据走同一条 GATT 的 AE01,
|
|
/// 私有通道 ABC1 由原生在升级期间挡掉。stage2 阶段设备会主动断开重启,
|
|
/// 那次「断开」是升级流程的一部分,不是失败——由原生判定,这里不看链路状态。
|
|
///
|
|
/// ⚠️ 上层 `DeviceOtaController` 的看门狗 90s 没进度就判失败,而 stage2 等待可达 3 分钟,
|
|
/// 原生在这段窗口每 15s 发一次 `stage2` 心跳事件喂它。
|
|
class SmartcarOtaPort implements DeviceOtaPort {
|
|
SmartcarOtaPort(this._link, this._plugin);
|
|
|
|
static const String _tag = 'SmartcarOtaPort';
|
|
|
|
final SmartcarLink _link;
|
|
final SmartcarDevicePlugin _plugin;
|
|
final StreamController<DeviceOtaProgress> _progress =
|
|
StreamController<DeviceOtaProgress>.broadcast();
|
|
|
|
StreamSubscription<Map<String, Object?>>? _sub;
|
|
bool _running = false;
|
|
bool _terminal = false;
|
|
bool _succeeded = false;
|
|
int _lastPercent = -1;
|
|
|
|
@override
|
|
bool get isRunning => _running;
|
|
|
|
@override
|
|
Stream<DeviceOtaProgress> get progressStream => _progress.stream;
|
|
|
|
@override
|
|
Future<void> 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;
|
|
_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<String, Object?> 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<void> 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<void> 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 {
|
|
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,
|
|
));
|
|
}
|
|
}
|
|
|