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/component/tool/cmd.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); } } // 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); } 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(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); // } // final totalFrames = // (firmwareData.length / UpgradeConfig.maxFrameSize).ceil(); // ef.log("[蓝牙指令执行日志] 固件总帧数:$totalFrames"); // 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); // await Future.delayed(Duration(milliseconds: sentFrames<500?100:10)); // } 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 _sendFirmwareFrames(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(); _updateUiProgress(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(); } } } 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; }, 3, 20000 // vota 命令可能需要更长时间 ); return result == true; } catch (e, stack) { ef.log("[vota命令异常] $e\n$stack"); return false; } } }