import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'package:easydevice/easydevice.dart'; import 'package:ef/ef.dart'; // THapp 所在的包 import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:http/http.dart' as http; import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart'; import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; import 'package:vbvs_app/enum/APPDeviceUpgrade.dart'; import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart'; import 'package:vbvs_app/pages/mh_page/device/upgrade/tool/device_upgrade_tool.dart'; // 固件升级配置(区分平台) class UpgradeConfig { static const int androidInitialFps = 50; static const int androidSubsequentFps = 150; static const int androidMaxFrameSize = 120; static const int iosInitialFps = 30; static const int iosSubsequentFps = 100; static const int iosMaxFrameSize = 100; static const Duration initialPhaseDuration = Duration(seconds: 15); static int get maxFrameSize => Platform.isAndroid ? androidMaxFrameSize : iosMaxFrameSize; static int getFps(Duration elapsedTime) { if (elapsedTime <= initialPhaseDuration) { return Platform.isAndroid ? androidInitialFps : iosInitialFps; } else { return Platform.isAndroid ? androidSubsequentFps : iosSubsequentFps; } } static Duration getFrameInterval(Duration elapsedTime) { final fps = getFps(elapsedTime); return Duration(milliseconds: (1000 / fps).round()); } } /// 升级任务状态 class UpgradeTask { final String mac; final THapp thapp; int progress; String status; Completer completer; DateTime startTime; DateTime? endTime; UpgradeTask({ required this.mac, required this.thapp, this.progress = 0, this.status = 'waiting', required this.completer, required this.startTime, }); } /// 多设备蓝牙固件升级管理器 class MultiDeviceFirmwareUpdater { static final MultiDeviceFirmwareUpdater _instance = MultiDeviceFirmwareUpdater._internal(); factory MultiDeviceFirmwareUpdater() => _instance; MultiDeviceFirmwareUpdater._internal(); final MHTBlueToothController _btController = Get.find(); final Map upgradeTasks = {}; final Map _firmwareDataCache = {}; final int _maxConcurrentUpgrades = 2; int _currentConcurrentUpgrades = 0; Future startUpgrade({ required THapp thapp, required String mac, required String firmwareUrl, }) async { if (upgradeTasks.containsKey(mac)) { final existingTask = upgradeTasks[mac]!; if (existingTask.status == 'upgrading' || existingTask.status == 'downloading') { throw Exception("[蓝牙指令执行日志] 设备 $mac 正在升级中".tr); } } // await sendBlogAndDlogCommands(thapp); final completer = Completer(); final task = UpgradeTask( mac: mac, thapp: thapp, completer: completer, startTime: DateTime.now(), ); upgradeTasks[mac] = task; _updateUiProgress(mac, 0, 'waiting'); _processUpgradeQueue(thapp); return completer.future; } void _processUpgradeQueue(THapp thapp) { final waitingTasks = upgradeTasks.values.where((task) => task.status == 'waiting').toList(); for (final task in waitingTasks) { if (_currentConcurrentUpgrades < _maxConcurrentUpgrades) { _currentConcurrentUpgrades++; _startSingleUpgrade(task, thapp); } } } Future _startSingleUpgrade(UpgradeTask task, THapp thapp) async { try { task.status = 'downloading'; if (!task.thapp.isConnected) { throw Exception("[蓝牙指令执行日志] 设备未连接".tr); } task.thapp.logingStream.listen((log) { ef.log("升级日志: $log"); }); FlutterBluePlus.setLogLevel(LogLevel.none); if (!_firmwareDataCache.containsKey(task.mac)) { await _downloadFirmware(task); } // await _sendCommand(task.thapp, "blog disable"); // _sendCommand(task.thapp, "blog disable"); await Future.delayed(Duration(milliseconds: 200)); task.status = 'upgrading'; _updateUiProgress(task.mac, 0, 'upgrading'); await _sendFirmwareFrames(task); // await _sendCommand(task.thapp, "blog enable"); await _verifyFirmware(task); task.status = 'completed'; task.endTime = DateTime.now(); _updateUiProgress(task.mac, 100, 'completed'); NewTopSlideNotification.show( text: "设备 ${task.mac} 固件升级成功".tr, textColor: Colors.green); MHTBlueToothController mhtBlueToothController = Get.find(); mhtBlueToothController.localUpgradeMac.remove(task.mac); mhtBlueToothController.updateAll(); task.completer.complete(); } catch (e, stack) { ef.log("[蓝牙指令执行日志] 设备 ${task.mac} 升级失败: $e\n$stack"); task.status = 'failed'; task.endTime = DateTime.now(); _updateUiProgress(task.mac, -1, 'failed'); // TopSlideNotification.show(Get.context!, // text: "设备 ${task.mac} 升级失败".tr, textColor: Colors.red); task.completer.completeError(e); } finally { _currentConcurrentUpgrades--; _cleanupTask(task.mac); _processUpgradeQueue(thapp); } } Future _downloadFirmware(UpgradeTask task) async { try { final firmwareUrl = _btController.currentUpgradeUrl; if (firmwareUrl == null) { throw Exception("[蓝牙指令执行日志] 固件URL为空".tr); } final response = await http.get(Uri.parse(firmwareUrl)); if (response.statusCode != 200) { throw Exception("[蓝牙指令执行日志] 固件下载失败,状态码:${response.statusCode}".tr); } _firmwareDataCache[task.mac] = response.bodyBytes; } catch (e, stack) { ef.log("[蓝牙指令执行日志] 下载固件失败: $e\n$stack"); throw Exception("[蓝牙指令执行日志] 固件下载失败".tr); } } Future _sendFirmwareFrames(UpgradeTask task) async { final firmwareData = _firmwareDataCache[task.mac]; if (firmwareData == null) { throw Exception("[蓝牙指令执行日志] 固件数据为空".tr); } try { int _lastLoggedProgress = -1; ef.log("[蓝牙指令执行日志] 开始执行 OTA 升级流程"); // 调用 THapp 封装的 otaStart 方法 int result = await task.thapp.otaStart( firmwareData, chunkSize: UpgradeConfig.maxFrameSize, // 单帧大小 retryPerChunk: 2, // 每帧重试次数 timeoutMs: 3000, // 每帧超时时间 firststepsframes: 300, // 前300帧延时发送,防止过载 firststepdelayms: 30, // 前300帧延迟 30ms onceDelayms: 10, // 之后帧间延迟 disableBlog: true, // 升级时关闭日志 withResponse: false, // 一般OTA不需要响应确认 binmode: false, // 使用文本命令模式发送(mmx命令) onProgress: (double progress) { // 更新 UI 进度 final percent = (progress * 100).clamp(0, 100).round(); _updateUiProgress(task.mac, percent, 'upgrading'); if (percent != _lastLoggedProgress) { ef.log("[升级进度]: ${percent}%"); } _lastLoggedProgress = percent; }, ); // 根据返回码判断状态 switch (result) { case 0: ef.log("[蓝牙指令执行日志] OTA 升级完成 ✅"); _updateUiProgress(task.mac, 100, 'completed'); break; case -1: throw Exception("固件为空或格式错误"); case -2: throw Exception("数据发送失败"); case -3: throw Exception("日志操作失败"); case -4: throw Exception("vota 命令执行失败"); default: throw Exception("未知错误: $result"); } } catch (e, stack) { ef.log("[蓝牙指令执行日志] OTA 升级失败: $e\n$stack"); rethrow; } } Future _sendAllFirmwareFrames(UpgradeTask task) async { final firmwareData = _firmwareDataCache[task.mac]; if (firmwareData == null) { throw Exception("[蓝牙指令执行日志] 固件数据为空".tr); } try { ef.log("[蓝牙指令执行日志] 开始执行 OTA 升级流程"); // 调用 THapp 封装的 otaStart 方法 int result = await task.thapp.otaStart( firmwareData, chunkSize: UpgradeConfig.maxFrameSize, // 单帧大小 retryPerChunk: 2, // 每帧重试次数 timeoutMs: 3000, // 每帧超时时间 firststepsframes: 300, // 前300帧延时发送,防止过载 firststepdelayms: 30, // 前300帧延迟 30ms onceDelayms: 10, // 之后帧间延迟 disableBlog: true, // 升级时关闭日志 withResponse: false, // 一般OTA不需要响应确认 binmode: false, // 使用文本命令模式发送(mmx命令) onProgress: (double progress) { // 更新 UI 进度 final percent = (progress * 100).clamp(0, 100).round(); _updateAllUiProgress(task.mac, percent, 'upgrading'); }, ); // 根据返回码判断状态 switch (result) { case 0: ef.log("[蓝牙指令执行日志] OTA 升级完成 ✅"); _updateUiProgress(task.mac, 100, 'completed'); break; case -1: throw Exception("固件为空或格式错误"); case -2: throw Exception("数据发送失败"); case -3: throw Exception("日志操作失败"); case -4: throw Exception("vota 命令执行失败"); default: throw Exception("未知错误: $result"); } } catch (e, stack) { ef.log("[蓝牙指令执行日志] OTA 升级失败: $e\n$stack"); rethrow; } } Future _verifyFirmware(UpgradeTask task) async { try { final success = await _sendVotaCommand(task.thapp); if (!success) { throw Exception("[蓝牙指令执行日志] 固件验证失败".tr); } await Future.delayed(Duration(seconds: 10)); bool deviceRestarted = false; for (int i = 0; i < 10; i++) { if (!task.thapp.isConnected) { deviceRestarted = true; break; } await Future.delayed(Duration(seconds: 1)); } if (!deviceRestarted) { throw Exception("[蓝牙指令执行日志] 设备未重启,可能升级失败".tr); } } catch (e, stack) { ef.log("[蓝牙指令执行日志] 固件校验失败: $e\n$stack"); throw Exception("[蓝牙指令执行日志] 固件校验失败".tr); } } Future _sendCommand(THapp thapp, String command, {bool needResponse = false}) async { try { if (needResponse) { // 需要响应的命令(如 vota 命令) return await thapp.send(command, true, // withResponse = true (log) { ef.log("[蓝牙指令执行日志] ${log.log},指令为:$command"); // 这里根据具体命令判断是否成功完成 if (log.log.contains("SUCCESS") || log.log.contains("OK") || log.log.contains("COMPLETE") || log.log.contains("successful")) { log.result = true; // 设置成功结果 return true; // 停止监听 } // 如果包含错误信息,也返回完成 if (log.log.contains("ERROR") || log.log.contains("FAIL") || log.log.contains("failed")) { log.result = false; // 设置失败结果 return true; // 停止监听 } return false; // 继续监听 }, 0, // retry = 0 15000 // timeoutms = 15000(15秒超时) ); } else { // 不需要响应的命令(如 mmx write、blog disable/enable) final success = await thapp.send( command, false, // withResponse = false null, // 不需要回调 0, // retry = 0 5000 // timeoutms = 5000(5秒超时) ); if (!success) { ef.log("[蓝牙指令执行日志] 命令发送可能失败: $command"); } return success; } } catch (e, stack) { ef.log("[蓝牙指令执行日志] 发送命令失败: $e\n$stack"); throw Exception("[蓝牙指令执行日志] 发送命令失败".tr); } } void _updateUiProgress(String mac, int progress, String status) { if (Get.isRegistered()) { final controller = Get.find(); if (controller.localUpgradeMac.containsKey(mac)) { controller.localUpgradeMac[mac] = { ...controller.localUpgradeMac[mac]!, "progress": progress, "status": status, "updateTime": DateTime.now().millisecondsSinceEpoch, }; // ef.log("[蓝牙指令执行日志] 更新进度: $progress, 状态: $status"); controller.update(); } } var upgradingDevices = mhtDeviceUpgradeController.model.upgradingDevices ?? []; for (var device in upgradingDevices) { if (device.mac == mac) { device.process = progress; device.upgradeStatus = _mapUpgradeStatus(status); break; } } // 如果设备不在列表中,选择性追加(可根据你项目逻辑决定是否添加) // if (!upgradingDevices.any((d) => d.mac == mac)) { // upgradingDevices.add(BlueToothDataModel( // bind: false, // mac: mac, // scanResult: ScanResult( // device: BluetoothDevice(remoteId: DeviceIdentifier(mac)), // advertisementData: AdvertisementData(localName: ''), // rssi: 0, // timeStamp: DateTime.now(), // ), // type: 0, // lastSeen: DateTime.now(), // process: progress, // upgradeStatus: _mapUpgradeStatus(status), // )); // } // ✅ 写回 model mhtDeviceUpgradeController.model.upgradingDevices = upgradingDevices; mhtDeviceUpgradeController.update(); } void _cleanupTask(String mac) { _firmwareDataCache.remove(mac); } void cancelUpgrade(String mac) { final task = upgradeTasks[mac]; if (task != null) { task.status = 'cancelled'; task.thapp.disconnect(); final upgradingDevices = mhtDeviceUpgradeController.model.upgradingDevices; if (upgradingDevices != null && upgradingDevices.isNotEmpty) { upgradingDevices.removeWhere((device) => device.mac == mac); } _updateUiProgress(mac, -1, 'cancelled'); task.completer.completeError(Exception("[蓝牙指令执行日志] 升级已取消".tr)); TopSlideNotification.show(Get.context!, text: "设备 $mac 升级已取消".tr); } } void cancelAllUpgrades() { for (final mac in upgradeTasks.keys) { cancelUpgrade(mac); } } Map? getUpgradeStatus(String mac) { final task = upgradeTasks[mac]; if (task == null) return null; return { 'mac': task.mac, 'progress': task.progress, 'status': task.status, 'startTime': task.startTime, 'endTime': task.endTime, }; } Map> getAllUpgradeStatus() { final Map> status = {}; for (final task in upgradeTasks.values) { status[task.mac] = { 'progress': task.progress, 'status': task.status, 'startTime': task.startTime, 'endTime': task.endTime, }; } return status; } bool isDeviceUpgrading(String mac) { final task = upgradeTasks[mac]; return task != null && (task.status == 'downloading' || task.status == 'upgrading'); } void cleanupCompletedTasks() { final completedMacs = upgradeTasks.entries .where((entry) => ['completed', 'failed', 'cancelled'].contains(entry.value.status)) .map((entry) => entry.key) .toList(); for (final mac in completedMacs) { upgradeTasks.remove(mac); } } Future _sendVotaCommand(THapp thapp) async { try { final result = await thapp.send('vota path="/root/mtd/update"', true, (log) { ef.log("[vota命令响应] ${log.log}"); // vota 命令特定的成功判断 if (log.log.contains("verification complete") || log.log.contains("update successful") || log.log.contains("rebooting")) { log.result = true; return true; } // vota 命令特定的失败判断 if (log.log.contains("verification failed") || log.log.contains("update error")) { log.result = false; return true; } return false; }, 3, 20000 // vota 命令可能需要更长时间 ); // return result == true; return true; } catch (e, stack) { ef.log("[vota命令异常] $e\n$stack"); return false; } } void _updateAllUiProgress(String mac, int progress, String status) { if (Get.isRegistered()) { final controller = Get.find(); if (controller.localUpgradeMac.containsKey(mac)) { controller.localUpgradeMac[mac] = { ...controller.localUpgradeMac[mac]!, "progress": progress, "status": status, "updateTime": DateTime.now().millisecondsSinceEpoch, }; // ef.log("[蓝牙指令执行日志] 更新进度: $progress, 状态: $status"); controller.update(); } } } int _mapUpgradeStatus(String status) { switch (status) { case 'upgrading': return APPDeviceUpgrade.upgrading.value; case 'success': return APPDeviceUpgrade.success.value; case 'failed': return APPDeviceUpgrade.fail.value; case 'waiting': return APPDeviceUpgrade.waiting.value; default: return APPDeviceUpgrade.nothing.value; } } }