import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:easydevice/easydevice.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart'; import 'package:ef/ef.dart'; // THapp 所在的包 // 固件升级配置(区分平台) 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); } } final completer = Completer(); final task = UpgradeTask( mac: mac, thapp: thapp, completer: completer, startTime: DateTime.now(), ); _upgradeTasks[mac] = task; _updateUiProgress(mac, 0, 'waiting'); _processUpgradeQueue(); return completer.future; } void _processUpgradeQueue() { final waitingTasks = _upgradeTasks.values.where((task) => task.status == 'waiting').toList(); for (final task in waitingTasks) { if (_currentConcurrentUpgrades < _maxConcurrentUpgrades) { _currentConcurrentUpgrades++; _startSingleUpgrade(task); } //screen shots } } Future _startSingleUpgrade(UpgradeTask task) async { try { task.status = 'downloading'; if (!task.thapp.isConnected) { throw Exception("[蓝牙指令执行日志] 设备未连接".tr); } 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'); TopSlideNotification.show(Get.context!, text: "设备 ${task.mac} 固件升级成功".tr, textColor: Colors.green); 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(); try { await _sendCommand(task.thapp, "blog enable"); } catch (e, stack) { ef.log("[蓝牙指令执行日志] 重新开启日志失败: $e\n$stack"); } } } 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); } final totalFrames = (firmwareData.length / UpgradeConfig.maxFrameSize).ceil(); final stopwatch = Stopwatch()..start(); DateTime lastFrameTime = DateTime.now(); for (int sentFrames = 0; sentFrames < totalFrames; sentFrames++) { if (task.status == 'cancelled') { throw Exception("[蓝牙指令执行日志] 升级已取消".tr); } final elapsed = stopwatch.elapsed; final frameInterval = UpgradeConfig.getFrameInterval(elapsed); final now = DateTime.now(); if (now.difference(lastFrameTime) < frameInterval) { await Future.delayed(Duration(milliseconds: 10)); continue; } final offset = sentFrames * UpgradeConfig.maxFrameSize; final end = offset + UpgradeConfig.maxFrameSize; final frameData = firmwareData.sublist( offset, end > firmwareData.length ? firmwareData.length : end, ); final base64Data = base64Encode(frameData); final mmxCommand = '''mmx path="/root/mtd/update" write base64 len=${frameData.length} offset=$offset data="$base64Data"'''; try { await _sendCommand(task.thapp, mmxCommand); } catch (e, stack) { ef.log("[蓝牙指令执行日志] 发送固件帧失败: $e\n$stack"); rethrow; } final waitTime = frameInterval - now.difference(lastFrameTime); if (waitTime > Duration.zero) await Future.delayed(waitTime); final progress = ((sentFrames + 1) / totalFrames * 100).round(); _updateUiProgress(task.mac, progress, 'upgrading'); lastFrameTime = now; } stopwatch.stop(); } 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, }; controller.update(); } } } void _cleanupTask(String mac) { _firmwareDataCache.remove(mac); } void cancelUpgrade(String mac) { final task = _upgradeTasks[mac]; if (task != null) { task.status = 'cancelled'; _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; }, 0, 20000 // vota 命令可能需要更长时间 ); return result == true; } catch (e, stack) { ef.log("[vota命令异常] $e\n$stack"); return false; } } }