diff --git a/assets/mhlangs/en_US.json b/assets/mhlangs/en_US.json index 6b8124b..e551a76 100644 --- a/assets/mhlangs/en_US.json +++ b/assets/mhlangs/en_US.json @@ -626,7 +626,8 @@ "立即升级": "Upgrade Now", "取消升级": "Cancel Upgrade", "通知设置": "Notification Setting", - "睡眠报告通知":"Sleep Report Notification", - "糖管家":"SweetyKeeper", - "蓝牙已断开,请点击下方刷新按钮重试": "Bluetooth disconnected, please click the refresh button below to retry" + "睡眠报告通知": "Sleep Report Notification", + "糖管家": "SweetyKeeper", + "蓝牙已断开,请点击下方刷新按钮重试": "Bluetooth disconnected, please click the refresh button below to retry", + "设备维护": "Device Maintenance" } \ No newline at end of file diff --git a/assets/mhlangs/zh_CN.json b/assets/mhlangs/zh_CN.json index c7f480e..0d8cdd0 100644 --- a/assets/mhlangs/zh_CN.json +++ b/assets/mhlangs/zh_CN.json @@ -629,5 +629,13 @@ "睡眠报告通知":"睡眠报告通知", "糖管家": "糖管家", "蓝牙连接成功": "蓝牙连接成功", - "蓝牙已断开,请点击下方刷新按钮重试": "蓝牙已断开,请点击下方刷新按钮重试" + "蓝牙已断开,请点击下方刷新按钮重试": "蓝牙已断开,请点击下方刷新按钮重试", + "设备维护": "设备维护", + "远程诊断": "远程诊断", + "批量升级": "批量升级", + "重置": "重置", + "升级": "升级", + "自动升级": "自动升级", + "超出数量限制,请等待": "超出数量限制,请等待" + } \ No newline at end of file diff --git a/assets/mhlangs/zh_TW.json b/assets/mhlangs/zh_TW.json index fb2911f..042941b 100644 --- a/assets/mhlangs/zh_TW.json +++ b/assets/mhlangs/zh_TW.json @@ -628,5 +628,6 @@ "睡眠报告通知":"睡眠報告通知", "糖管家":"糖管家", "蓝牙连接成功":"糖管家", - "蓝牙已断开,请点击下方刷新按钮重试": "蓝牙已斷開,請點擊下方刷新按鈕重試" + "蓝牙已断开,请点击下方刷新按钮重试": "蓝牙已斷開,請點擊下方刷新按鈕重試", + "设备维护": "設備維護" } \ No newline at end of file diff --git a/lib/common/util/BluetoothFirmwareUpdater.dart b/lib/common/util/BluetoothFirmwareUpdater.dart index 5da0f91..ebd18ba 100644 --- a/lib/common/util/BluetoothFirmwareUpdater.dart +++ b/lib/common/util/BluetoothFirmwareUpdater.dart @@ -1,16 +1,17 @@ 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 所在的包 +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 { @@ -70,7 +71,7 @@ class MultiDeviceFirmwareUpdater { final MHTBlueToothController _btController = Get.find(); - final Map _upgradeTasks = {}; + final Map upgradeTasks = {}; final Map _firmwareDataCache = {}; final int _maxConcurrentUpgrades = 2; int _currentConcurrentUpgrades = 0; @@ -80,8 +81,8 @@ class MultiDeviceFirmwareUpdater { required String mac, required String firmwareUrl, }) async { - if (_upgradeTasks.containsKey(mac)) { - final existingTask = _upgradeTasks[mac]!; + if (upgradeTasks.containsKey(mac)) { + final existingTask = upgradeTasks[mac]!; if (existingTask.status == 'upgrading' || existingTask.status == 'downloading') { throw Exception("[蓝牙指令执行日志] 设备 $mac 正在升级中".tr); @@ -96,7 +97,7 @@ class MultiDeviceFirmwareUpdater { startTime: DateTime.now(), ); - _upgradeTasks[mac] = task; + upgradeTasks[mac] = task; _updateUiProgress(mac, 0, 'waiting'); _processUpgradeQueue(thapp); @@ -106,7 +107,7 @@ class MultiDeviceFirmwareUpdater { void _processUpgradeQueue(THapp thapp) { final waitingTasks = - _upgradeTasks.values.where((task) => task.status == 'waiting').toList(); + upgradeTasks.values.where((task) => task.status == 'waiting').toList(); for (final task in waitingTasks) { if (_currentConcurrentUpgrades < _maxConcurrentUpgrades) { @@ -123,7 +124,10 @@ class MultiDeviceFirmwareUpdater { 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); } @@ -144,9 +148,11 @@ class MultiDeviceFirmwareUpdater { task.endTime = DateTime.now(); _updateUiProgress(task.mac, 100, 'completed'); - TopSlideNotification.show(Get.context!, + 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"); @@ -186,69 +192,68 @@ class MultiDeviceFirmwareUpdater { } } - // 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 { + 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 升级流程"); @@ -267,7 +272,7 @@ class MultiDeviceFirmwareUpdater { onProgress: (double progress) { // 更新 UI 进度 final percent = (progress * 100).clamp(0, 100).round(); - _updateUiProgress(task.mac, percent, 'upgrading'); + _updateAllUiProgress(task.mac, percent, 'upgrading'); }, ); @@ -388,6 +393,38 @@ class MultiDeviceFirmwareUpdater { 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) { @@ -395,9 +432,15 @@ class MultiDeviceFirmwareUpdater { } void cancelUpgrade(String mac) { - final task = _upgradeTasks[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)); @@ -406,13 +449,13 @@ class MultiDeviceFirmwareUpdater { } void cancelAllUpgrades() { - for (final mac in _upgradeTasks.keys) { + for (final mac in upgradeTasks.keys) { cancelUpgrade(mac); } } Map? getUpgradeStatus(String mac) { - final task = _upgradeTasks[mac]; + final task = upgradeTasks[mac]; if (task == null) return null; return { @@ -426,7 +469,7 @@ class MultiDeviceFirmwareUpdater { Map> getAllUpgradeStatus() { final Map> status = {}; - for (final task in _upgradeTasks.values) { + for (final task in upgradeTasks.values) { status[task.mac] = { 'progress': task.progress, 'status': task.status, @@ -438,20 +481,20 @@ class MultiDeviceFirmwareUpdater { } bool isDeviceUpgrading(String mac) { - final task = _upgradeTasks[mac]; + final task = upgradeTasks[mac]; return task != null && (task.status == 'downloading' || task.status == 'upgrading'); } void cleanupCompletedTasks() { - final completedMacs = _upgradeTasks.entries + 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); + upgradeTasks.remove(mac); } } @@ -480,10 +523,42 @@ class MultiDeviceFirmwareUpdater { }, 3, 20000 // vota 命令可能需要更长时间 ); - return result == true; + // 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; + } + } } diff --git a/lib/common/util/FirmwareVersionService.dart b/lib/common/util/FirmwareVersionService.dart index 77504c5..f5dbfbc 100644 --- a/lib/common/util/FirmwareVersionService.dart +++ b/lib/common/util/FirmwareVersionService.dart @@ -29,14 +29,14 @@ class FirmwareVersionService { try { // 构建完整的API URL final apiUrl = '$baseUrl/~/api/get_file_list?uri=$firmwarePath'; - + // 发送HTTP请求 - 使用基础的http客户端,不依赖项目特定实现 final response = await _makeHttpRequest(apiUrl); - + if (response == null) { return null; } - + // 解析响应数据 return _parseFirmwareData(response, baseUrl, firmwarePath); } catch (e) { @@ -50,14 +50,14 @@ class FirmwareVersionService { try { // 使用基础的http客户端 final httpClient = HttpClient(); - + final request = await httpClient.getUrl(Uri.parse(url)); final response = await request.close(); - + if (response.statusCode != 200) { return null; } - + final responseBody = await response.transform(utf8.decoder).join(); return jsonDecode(responseBody); } catch (e) { @@ -68,14 +68,11 @@ class FirmwareVersionService { /// 解析固件数据 static FirmwareVersionInfo? _parseFirmwareData( - dynamic data, - String baseUrl, - String firmwarePath - ) { + dynamic data, String baseUrl, String firmwarePath) { if (data == null || data['list'] == null) { return null; } - + final versionList = data['list'] as List; int? maxVersionNum; String? latestFileName; @@ -86,10 +83,10 @@ class FirmwareVersionService { try { // 解析版本号 - 假设文件名格式为 "something-version.extension" final versionMatch = _extractVersionFromFileName(fileName); - + if (versionMatch != null) { final versionNum = int.parse(versionMatch); - + if (maxVersionNum == null || versionNum > maxVersionNum) { maxVersionNum = versionNum; latestFileName = fileName; @@ -104,15 +101,16 @@ class FirmwareVersionService { if (latestFileName != null && maxVersionNum != null) { // 构建下载URL - final downloadUrl = '$baseUrl/${firmwarePath.replaceFirst('/', '')}$latestFileName'; - + final downloadUrl = + '$baseUrl/${firmwarePath.replaceFirst('/', '')}$latestFileName'; + return FirmwareVersionInfo( version: maxVersionNum.toString(), fileName: latestFileName, downloadUrl: downloadUrl, ); } - + return null; } @@ -127,14 +125,63 @@ class FirmwareVersionService { // 格式: firmware_123.bin RegExp(r'_(\d+)\.'), ]; - + for (final pattern in patterns) { final match = pattern.firstMatch(fileName); if (match != null && match.groupCount >= 1) { return match.group(1); } } - + return null; } -} \ No newline at end of file + + /// 获取所有固件版本(按版本号从大到小排序) + static Future> getAllFirmwareVersions({ + required String baseUrl, + required String firmwarePath, + }) async { + try { + final apiUrl = '$baseUrl/~/api/get_file_list?uri=$firmwarePath'; + final response = await _makeHttpRequest(apiUrl); + if (response == null) return []; + + return _parseAllFirmwareData(response, baseUrl, firmwarePath); + } catch (e) { + print('获取所有固件版本信息失败: $e'); + return []; + } + } + + /// 解析所有固件版本数据 + static List _parseAllFirmwareData( + dynamic data, String baseUrl, String firmwarePath) { + if (data == null || data['list'] == null) { + return []; + } + + final versionList = data['list'] as List; + final result = []; + + for (var item in versionList) { + if (item is Map && item.containsKey('n')) { + final fileName = item['n'] as String; + final versionStr = _extractVersionFromFileName(fileName); + if (versionStr != null) { + final downloadUrl = + '$baseUrl/${firmwarePath.replaceFirst('/', '')}$fileName'; + result.add(FirmwareVersionInfo( + version: versionStr, + fileName: fileName, + downloadUrl: downloadUrl, + )); + } + } + } + + // 按版本号从大到小排序 + result.sort((a, b) => int.parse(b.version).compareTo(int.parse(a.version))); + + return result; + } +} diff --git a/lib/component/tool/NewTopSlideNotification.dart b/lib/component/tool/NewTopSlideNotification.dart new file mode 100644 index 0000000..1b50e03 --- /dev/null +++ b/lib/component/tool/NewTopSlideNotification.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:get/get.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; + + +class NewTopSlideNotification extends StatefulWidget { + final String text; + final double fontSize; + final Color? textColor; + final double slideOffset; + final Duration duration; + + const NewTopSlideNotification({ + super.key, + required this.text, + required this.fontSize, + this.textColor, + required this.slideOffset, + required this.duration, + }); + + static OverlayEntry? _currentEntry; + static bool _isShowing = false; + + /// ✅ 不需要传 context + static void show({ + String text = '操作成功!', + double fontSize = 16, + Color? textColor, + double slideOffset = 300.0, + Duration duration = const Duration(seconds: 2), + }) { + _showInternal( + text: text, + fontSize: fontSize, + textColor: textColor, + slideOffset: slideOffset, + duration: duration, + ); + } + + static void _showInternal({ + required String text, + required double fontSize, + Color? textColor, + required double slideOffset, + required Duration duration, + }) { + // 移除现有弹窗 + _removeCurrentEntry(); + + // ✅ 自动获取全局可用的 context + final context = Get.overlayContext ?? Get.context; + if (context == null) { + debugPrint('NewTopSlideNotification: 无法获取有效的上下文'); + return; + } + + final overlay = Overlay.of(context); + if (overlay == null) { + debugPrint('NewTopSlideNotification: 未找到 Overlay'); + return; + } + + final entry = OverlayEntry( + builder: (_) => NewTopSlideNotification( + text: text, + fontSize: fontSize, + textColor: textColor, + slideOffset: slideOffset, + duration: duration, + ), + ); + + _currentEntry = entry; + _isShowing = true; + overlay.insert(entry); + + // 自动移除 + Future.delayed(duration + const Duration(milliseconds: 500), () { + _removeCurrentEntry(); + }); + } + + static void _removeCurrentEntry() { + if (_currentEntry != null && _isShowing) { + _currentEntry!.remove(); + _currentEntry = null; + _isShowing = false; + } + } + + @override + State createState() => _TopSlideNotificationState(); +} + +class _TopSlideNotificationState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + bool _isAnimating = false; + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + + SchedulerBinding.instance.addPostFrameCallback((_) { + _startAnimation(); + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + final screenHeight = MediaQuery.of(context).size.height; + final offsetValue = widget.slideOffset / screenHeight; + + _animation = Tween( + begin: const Offset(0, -1), + end: Offset(0, offsetValue), + ).animate(CurvedAnimation( + parent: _controller, + curve: Curves.easeOut, + reverseCurve: Curves.easeIn, + )); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Color get _textColor => + widget.textColor ?? Get.find().currentColor.sc2; + + @override + Widget build(BuildContext context) { + return Positioned( + top: 140.rpx, + left: 0, + right: 0, + child: SlideTransition( + position: _animation, + child: Material( + color: stringToColor("#000000").withOpacity(0.8), + child: Padding( + padding: EdgeInsets.symmetric(vertical: 20.rpx), + child: Center( + child: Text( + widget.text, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: widget.fontSize, + color: _textColor, + ), + ), + ), + ), + ), + ), + ); + } + + Future _startAnimation() async { + if (_isAnimating) return; + _isAnimating = true; + + try { + await _controller.forward(); + await Future.delayed(widget.duration); + if (mounted) { + await _controller.reverse(); + } + } finally { + _isAnimating = false; + } + } +} diff --git a/lib/component/tool/SelectableTagButton.dart b/lib/component/tool/SelectableTagButton.dart index 8d01cf8..13da935 100644 --- a/lib/component/tool/SelectableTagButton.dart +++ b/lib/component/tool/SelectableTagButton.dart @@ -3,48 +3,57 @@ import 'package:flutter/material.dart'; import 'package:vbvs_app/common/color/appConstants.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; - import 'CustomCard.dart'; class SelectableTagButton extends StatelessWidget { final String label; final bool selected; final VoidCallback onTap; - final double minWidth; // 最小宽度,单位:dp(会转 rpx) - final double maxWidth; // 最大宽度,单位:dp(会转 rpx) + final double minWidth; // 最小宽度 + final double maxWidth; // 最大宽度 + final double borderRadius; // ✅ 圆角(默认 12) + final List? colors; // ✅ 可选渐变颜色 + final Color? selectedTextColor; // ✅ 选中文字颜色 + final Color? unselectedTextColor; // ✅ 未选中文字颜色 - ThemeController themeController = Get.find(); + final ThemeController themeController = Get.find(); SelectableTagButton({ Key? key, required this.label, required this.selected, required this.onTap, - this.minWidth = 132, // 默认最小宽度 - this.maxWidth = 500, // 默认最大宽度 + this.minWidth = 132, + this.maxWidth = 500, + this.borderRadius = 12.0, + this.colors, + this.selectedTextColor, + this.unselectedTextColor, }) : super(key: key); @override Widget build(BuildContext context) { final double minWidthRpx = minWidth.rpx; final double maxWidthRpx = maxWidth.rpx; - final double horizontalPadding = 28.rpx * 2; // 左右各 28,总 56 - // 估算文本宽度:每个字符按 14.rpx 字号估一个宽度 + final double horizontalPadding = 28.rpx * 2; final double estimatedTextWidth = label.length * 33.rpx; - // 总宽度 = 文本宽度 + padding final double totalWidth = estimatedTextWidth + horizontalPadding; - - // 限制在 min 和 max 之间 final double constrainedWidth = totalWidth.clamp(minWidthRpx, maxWidthRpx); + // ✅ 背景渐变颜色 + final List effectiveColors = colors ?? + [themeController.currentColor.sc1, themeController.currentColor.sc2]; + + // ✅ 字体颜色逻辑 + final Color effectiveTextColor = selected + ? (selectedTextColor ?? themeController.currentColor.sc3) + : (unselectedTextColor ?? themeController.currentColor.sc4); + return CustomCard( onTap: onTap, - borderRadius: AppConstants().normal_container_radius, - colors: selected - ? [themeController.currentColor.sc1, themeController.currentColor.sc2] - : [Colors.transparent], - // colors: [Colors.transparent], + borderRadius: borderRadius, + colors: selected ? effectiveColors : [Colors.transparent], enableGradient: true, child: Container( decoration: BoxDecoration( @@ -53,8 +62,8 @@ class SelectableTagButton extends StatelessWidget { : Border.all( color: themeController.currentColor.sc4, width: 0.5.rpx, - ), // 未选中时无边框 - borderRadius: BorderRadius.circular(12.0), // 如果需要圆角 + ), + borderRadius: BorderRadius.circular(borderRadius), ), padding: EdgeInsets.symmetric(horizontal: 28.rpx), constraints: BoxConstraints( @@ -67,10 +76,8 @@ class SelectableTagButton extends StatelessWidget { overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle( - color: selected - ? themeController.currentColor.sc3 - : themeController.currentColor.sc4, - fontSize: AppConstants().normal_text_fontSize, // 字体也用 rpx 控制 + color: effectiveTextColor, + fontSize: AppConstants().normal_text_fontSize, ), ), ), diff --git a/lib/component/tool/TopSlideNotification.dart b/lib/component/tool/TopSlideNotification.dart index 5076c9b..302f0a8 100644 --- a/lib/component/tool/TopSlideNotification.dart +++ b/lib/component/tool/TopSlideNotification.dart @@ -7,7 +7,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; class TopSlideNotification extends StatefulWidget { - BuildContext?context; + BuildContext? context; final String text; double? fontSize = 26.rpx; Color? textColor; diff --git a/lib/controller/device/blueteeth_bind_controller.g.dart b/lib/controller/device/blueteeth_bind_controller.g.dart index f4beb3b..d884e0e 100644 --- a/lib/controller/device/blueteeth_bind_controller.g.dart +++ b/lib/controller/device/blueteeth_bind_controller.g.dart @@ -10,6 +10,7 @@ BlueteethBindModel _$BlueteethBindModelFromJson(Map json) => BlueteethBindModel() ..read = (json['read'] as num?)?.toInt() ..singal = (json['singal'] as num?)?.toDouble() + ..bluetooth = json['bluetooth'] as bool? ..bindArr = json['bindArr'] as List ..connectedWifiName = json['connectedWifiName'] as String ..connectedRssi = (json['connectedRssi'] as num).toInt() @@ -24,6 +25,7 @@ Map _$BlueteethBindModelToJson(BlueteethBindModel instance) => { 'read': instance.read, 'singal': instance.singal, + 'bluetooth': instance.bluetooth, 'bindArr': instance.bindArr, 'connectedWifiName': instance.connectedWifiName, 'connectedRssi': instance.connectedRssi, diff --git a/lib/controller/message/common_message_setting_controller.g.dart b/lib/controller/message/common_message_setting_controller.g.dart index 6559b16..4c4b5ec 100644 --- a/lib/controller/message/common_message_setting_controller.g.dart +++ b/lib/controller/message/common_message_setting_controller.g.dart @@ -14,7 +14,8 @@ CommonMessageSettingModel _$CommonMessageSettingModelFromJson( ..serviceSetting = (json['serviceSetting'] as num?)?.toInt() ..tipSetting = (json['tipSetting'] as num?)?.toInt() ..deviceUpgradeSetting = (json['deviceUpgradeSetting'] as num?)?.toInt() - ..deviceIssueSetting = (json['deviceIssueSetting'] as num?)?.toInt(); + ..deviceIssueSetting = (json['deviceIssueSetting'] as num?)?.toInt() + ..sleepReportSetting = (json['sleepReportSetting'] as num?)?.toInt(); Map _$CommonMessageSettingModelToJson( CommonMessageSettingModel instance) => @@ -25,4 +26,5 @@ Map _$CommonMessageSettingModelToJson( 'tipSetting': instance.tipSetting, 'deviceUpgradeSetting': instance.deviceUpgradeSetting, 'deviceIssueSetting': instance.deviceIssueSetting, + 'sleepReportSetting': instance.sleepReportSetting, }; diff --git a/lib/controller/user_info_controller.g.dart b/lib/controller/user_info_controller.g.dart index a0bde94..87fa3ad 100644 --- a/lib/controller/user_info_controller.g.dart +++ b/lib/controller/user_info_controller.g.dart @@ -20,7 +20,8 @@ UserInfoModel _$UserInfoModelFromJson(Map json) => ..appVersion = json['appVersion'] as String? ..login = (json['login'] as num?)?.toInt() ..deviceBindNum = (json['deviceBindNum'] as num?)?.toInt() - ..loginPhone = (json['loginPhone'] as num?)?.toInt(); + ..loginPhone = (json['loginPhone'] as num?)?.toInt() + ..isProgrammaticPop = json['isProgrammaticPop'] as bool; Map _$UserInfoModelToJson(UserInfoModel instance) => { @@ -35,4 +36,5 @@ Map _$UserInfoModelToJson(UserInfoModel instance) => 'login': instance.login, 'deviceBindNum': instance.deviceBindNum, 'loginPhone': instance.loginPhone, + 'isProgrammaticPop': instance.isProgrammaticPop, }; diff --git a/lib/enum/APPDeviceUpgrade.dart b/lib/enum/APPDeviceUpgrade.dart new file mode 100644 index 0000000..16c1639 --- /dev/null +++ b/lib/enum/APPDeviceUpgrade.dart @@ -0,0 +1,93 @@ +import 'package:ef/ef.dart'; + +enum APPDeviceUpgrade { + ALL, // 全部 1 + success, // 成功 2 + fail, // 失败 3 + waiting, // 等待中 4 + upgrading, // 升级中 5 + nothing, // 扫描中 6(新加) +} + +extension APPDeviceUpgradeExtension on APPDeviceUpgrade { + /// 获取整型值 + int get value { + switch (this) { + case APPDeviceUpgrade.ALL: + return 1; + case APPDeviceUpgrade.success: + return 2; + case APPDeviceUpgrade.fail: + return 3; + case APPDeviceUpgrade.waiting: + return 4; + case APPDeviceUpgrade.upgrading: + return 5; + case APPDeviceUpgrade.nothing: + return 6; + } + } + + /// 根据整型值解析为枚举 + static APPDeviceUpgrade? fromInt(int? type) { + switch (type) { + case 1: + return APPDeviceUpgrade.ALL; + case 2: + return APPDeviceUpgrade.success; + case 3: + return APPDeviceUpgrade.fail; + case 4: + return APPDeviceUpgrade.waiting; + case 5: + return APPDeviceUpgrade.upgrading; + case 6: + return APPDeviceUpgrade.nothing; + default: + return null; + } + } + + /// 根据整型值返回对应名称字符串 + static String? nameFromInt(int? type) { + final upgrade = fromInt(type); + return upgrade?.description; + } + + /// 获取描述文本 + String get description { + switch (this) { + case APPDeviceUpgrade.ALL: + return "全部".tr; + case APPDeviceUpgrade.success: + return "成功".tr; + case APPDeviceUpgrade.fail: + return "失败".tr; + case APPDeviceUpgrade.waiting: + return "等待中".tr; + case APPDeviceUpgrade.upgrading: + return "升级中".tr; + case APPDeviceUpgrade.nothing: + return "扫描中".tr; + } + } + + /// 下拉选项用 - label 列表(展示) + static List get labelList => APPDeviceUpgrade.values + .where((e) => e != APPDeviceUpgrade.nothing) + .map((e) => e.description) + .toList(); + + /// 下拉选项用 - value 列表(对应值) + static List get valueList => APPDeviceUpgrade.values + .where((e) => e != APPDeviceUpgrade.nothing) + .map((e) => e.value) + .toList(); + + /// 下拉选项用 - map 列表(不含 nothing) + static List> get list => APPDeviceUpgrade.values + .where((e) => e != APPDeviceUpgrade.nothing) + .map((e) => {"id": e.value, "name": e.description}) + .toList(); + +} diff --git a/lib/main.dart b/lib/main.dart index 0c243fa..34a839e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,10 +6,8 @@ import 'package:EasyDartModule/base/logger/Logger.dart'; import 'package:EasyDartModule/base/websocket/WebSocket.dart'; import 'package:easyweb/utils/appmanger.dart'; import 'package:ef/ef.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:fluwx/fluwx.dart'; import 'package:get_storage/get_storage.dart'; @@ -77,6 +75,7 @@ import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart'; import 'package:vbvs_app/pages/mh_page/MattressControl.dart'; import 'package:vbvs_app/pages/mh_page/device/component/mht_device_calibration_controller.dart'; import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart'; +import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade_controller.dart'; import 'package:vbvs_app/pages/mh_page/homepage/controller/mht_home_controller.dart'; import 'package:vbvs_app/pages/mh_page/test/WebviewTestModel.dart'; import 'package:vbvs_app/pages/mh_page/user/controller/bind_tel_controller.dart'; @@ -902,6 +901,7 @@ class MyApp extends StatelessWidget { Get.lazyPut(() => PrivacyPdfController()), Get.lazyPut(() => AuthBindTelController()), Get.lazyPut(() => CommonMessageSettingController()), + Get.lazyPut(() => MHTDeviceUpgradeController()), ])); } diff --git a/lib/model/user_data.dart b/lib/model/user_data.dart index 6902649..0c9afaa 100644 --- a/lib/model/user_data.dart +++ b/lib/model/user_data.dart @@ -17,6 +17,7 @@ class UserModel { int? created_at; String? email; bool? test; + bool? fac;//是否测试,可以全部升级 UserModel(); static UserModel fromJson(Map json) => diff --git a/lib/model/user_data.g.dart b/lib/model/user_data.g.dart index 7d25e07..58703c0 100644 --- a/lib/model/user_data.g.dart +++ b/lib/model/user_data.g.dart @@ -18,7 +18,9 @@ UserModel _$UserModelFromJson(Map json) => UserModel() ..deleted = (json['deleted'] as num?)?.toInt() ..status = json['status'] as String? ..created_at = (json['created_at'] as num?)?.toInt() - ..email = json['email'] as String?; + ..email = json['email'] as String? + ..test = json['test'] as bool? + ..fac = json['fac'] as bool?; Map _$UserModelToJson(UserModel instance) => { 'uid': instance.uid, @@ -33,4 +35,6 @@ Map _$UserModelToJson(UserModel instance) => { 'status': instance.status, 'created_at': instance.created_at, 'email': instance.email, + 'test': instance.test, + 'fac': instance.fac, }; diff --git a/lib/pages/device/instant_body_page.dart b/lib/pages/device/instant_body_page.dart index d64359c..c9f943a 100644 --- a/lib/pages/device/instant_body_page.dart +++ b/lib/pages/device/instant_body_page.dart @@ -91,9 +91,11 @@ class _InstantBodyPageState extends State bodyMotion = data['bodyMotion'] == null ? -1 : data['bodyMotion']; breathrate = data["breathRate"] == null ? -1 : data["breathRate"]; heartrate = data['heartRate'] == null ? -1 : data['heartRate']; - snores = data['snores'] == null || data['snores'] == "" + snores = data['snores'] == null || + data['snores'] == "" || + data['snores'] == "否".tr ? "否".tr - : "是".tr; + : "${data['snores']}".tr; } if (mounted) { diff --git a/lib/pages/device_bind/wifi_page.dart b/lib/pages/device_bind/wifi_page.dart index 27a667a..70b7454 100644 --- a/lib/pages/device_bind/wifi_page.dart +++ b/lib/pages/device_bind/wifi_page.dart @@ -42,6 +42,14 @@ class _WifiPageState extends State { ThemeController themeController = Get.find(); var lisObj; + bool haveSuccess = false; + bool dealing = false; //是否正在刷新 + bool _isDisposed = false; + Timer? _timeoutTimer; + StreamSubscription>? _scanSubscription; + ScanResult? targetDevice; + DateTime _lastTapTime = DateTime.now(); + @override void initState() { super.initState(); @@ -87,11 +95,6 @@ class _WifiPageState extends State { Get.toNamed("/calibrationPage", arguments: 1); }); } else if (aa == 'unknown') { - // TopSlideNotification.show( - // context, - // text: "获取设备网络类型失败".tr, - // textColor: themeController.currentColor.sc9, - // ); blueteethBindController.netType.value = 3; blueteethBindController.updateAll(); WidgetsBinding.instance.addPostFrameCallback((_) { @@ -120,13 +123,20 @@ class _WifiPageState extends State { }); } + // @override + // void dispose() { + // super.dispose(); + // if (lisObj != null) { + // lisObj.cancel(); + // } + // blueteethBindController.currentDevice!.disconnect(); + // } @override void dispose() { + _isDisposed = true; + _cleanupResources(); + // _disconnectDevice(); super.dispose(); - if (lisObj != null) { - lisObj.cancel(); - } - blueteethBindController.currentDevice!.disconnect(); } @override @@ -568,8 +578,7 @@ class _WifiPageState extends State { wifiItem['ssid'] ?? '未命名'.tr, onConfirm: () async { - // showLoadingDialog( - // context); // 显示 loading + haveSuccess = false; blueteethBindController .selectWifi .value = wifiItem; @@ -580,22 +589,48 @@ class _WifiPageState extends State { blueteethBindController .currentDevice!); // Navigator.pop(context); + bool memoryFlag = + await queryMemory( + blueteethBindController + .currentDevice!); + if (!memoryFlag) { + await rebootDevice( + blueteethBindController + .currentDevice!); + await dealWifi( + blueteethBindController + .currentDeviceMac + .value, + needSuccess: true); + // return; + } + if (!flag) { + blueteethBindController + .selectWifi + .value = {}; + return; + } if (flag) { - // bool wifiStatus = - // await getWifiStatus( - // blueteethBindController - // .currentDevice!); - bool wifiStatus = false; var aa = await getDeviceWifiStatus( blueteethBindController .currentDevice!, - 1); + 1, + link: true); + aa = await restoreWifi( + blueteethBindController + .currentDevice!, + aa); + if (!memoryFlag) { + aa = await rebootDeviceCurrent( + blueteethBindController + .currentDevice!, + aa); + } blueteethBindController .selectWifi .value = {}; if (aa != null && aa is Map) { - wifiStatus = true; blueteethBindController .connect_wifi .value = aa; @@ -603,70 +638,73 @@ class _WifiPageState extends State { blueteethBindController .connect_wifi .value = {}; + blueteethBindController + .wifiStatus + .value == + 0; } blueteethBindController .wifiStatus .value = - wifiStatus == true + (aa != null && + aa != false) ? 1 : 0; - blueteethBindController - .wifiStatus - .value = - wifiStatus == true - ? 1 - : 0; - if (wifiStatus) { + if (aa != null && + aa != false) { updateDeviceBindStatus( blueteethBindController .currentDeviceMac! .value); - // Navigator.pop(context); - TopSlideNotification - .show( - context, - text: "wifi页.配网成功".tr, - textColor: - themeController - .currentColor - .sc2, - ); + if (!haveSuccess) { + TopSlideNotification + .show( + context, + text: "配网成功".tr, + textColor: + themeController + .currentColor + .sc2, + ); + } + } else { + //读取不到wifi信息 + blueteethBindController + .connect_wifi + .value = {}; + blueteethBindController + .selectWifi + .value = {}; blueteethBindController .wifiStatus - .value = 1; - blueteethBindController - .updateAll(); - } else { - // Navigator.pop(context); + .value = 0; TopSlideNotification .show( context, - text: "wifi页.配网失败".tr, + text: "配网失败".tr, textColor: themeController .currentColor .sc9, ); - blueteethBindController - .wifiStatus - .value = 0; - blueteethBindController - .updateAll(); } } else { - // Navigator.pop(context); + blueteethBindController + .connect_wifi + .value = {}; + blueteethBindController + .selectWifi + .value = {}; + blueteethBindController + .wifiStatus.value = 0; TopSlideNotification.show( context, - text: "wifi页.配网失败".tr, + text: "配网失败".tr, textColor: themeController .currentColor .sc9, ); - blueteethBindController - .wifiStatus.value = 0; - blueteethBindController - .updateAll(); } }); }, @@ -836,98 +874,244 @@ class _WifiPageState extends State { ); } - Future initWifiStatusAndWifiList() async { + // Future initWifiStatusAndWifiList() async { + // if (lisObj != null) { + // lisObj!.cancel(); + // } + // bool wifiStatus = false; + // var aa = + // await getDeviceWifiStatus(blueteethBindController.currentDevice!, 1); + // if (aa != null && aa is Map) { + // wifiStatus = true; + // updateDeviceBindStatus(blueteethBindController.currentDeviceMac!.value); + // blueteethBindController.connect_wifi.value = aa; + // } else { + // wifiStatus = false; + // } + // blueteethBindController.wifiStatus.value = wifiStatus == true ? 1 : 0; + // List wifiList = []; + // try { + // final result = await getWifiList(blueteethBindController.currentDevice!); + // if (result is List) { + // wifiList = result; + // } + // } catch (e) { + // print("异常: $e"); + // } + // if (wifiList.length > 0) { + // blueteethBindController.connectStatus.value = 1; + // blueteethBindController.updateAll(); + // // Navigator.pop(context); + // TopSlideNotification.show( + // context, + // text: "获取wifi列表成功".tr, + // textColor: themeController.currentColor.sc2, + // ); + // blueteethBindController.wifiList.value = wifiList; + // blueteethBindController.updateAll(); + // } else { + // // Navigator.pop(context); + // TopSlideNotification.show( + // context, + // text: "获取wifi列表失败".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } + // lisObj = blueteethBindController.currentDevice!.statusStream + // .listen((onData) async { + // if (onData.status == BleEventType.recvLineLog) { + // final line = onData.val; + // print("[bleee]:" + line); + // edm.EasyDartModule.logger.info("[bleee]:" + line); + // DailyLogUtils.writeLog("[bleee]:" + line); + // } + // // if (onData.status == BleEventType.ready) { + // // // showLoadingDialog(context, title: "获取wifi列表中...".tr); + // // bool wifiStatus = false; + // // var aa = await getDeviceWifiStatus( + // // blueteethBindController.currentDevice!, 1); + // // if (aa != null && aa is Map) { + // // wifiStatus = true; + // // blueteethBindController.connect_wifi.value = aa; + // // } + // // blueteethBindController.wifiStatus.value = wifiStatus == true ? 1 : 0; + // // List wifiList = []; + // // try { + // // final result = + // // await getWifiList(blueteethBindController.currentDevice!); + // // if (result is List) { + // // wifiList = result; + // // } + // // } catch (e) { + // // print("异常: $e"); + // // } + // // if (wifiList.length > 0) { + // // blueteethBindController.connectStatus.value = 1; + // // blueteethBindController.updateAll(); + // // // Navigator.pop(context); + // // TopSlideNotification.show( + // // context, + // // text: "获取wifi列表成功".tr, + // // textColor: themeController.currentColor.sc2, + // // ); + // // blueteethBindController.wifiList.value = wifiList; + // // blueteethBindController.updateAll(); + // // } else { + // // Navigator.pop(context); + // // TopSlideNotification.show( + // // context, + // // text: "获取wifi列表失败".tr, + // // textColor: themeController.currentColor.sc9, + // // ); + // // } + // // } + // }); + // } + + Future initWifiStatusAndWifiList( + {int retryCount = 0, bool needSuccess = false}) async { + if (_isDisposed) return; + + // 取消之前的监听器 if (lisObj != null) { lisObj!.cancel(); + lisObj = null; } + bool wifiStatus = false; - var aa = - await getDeviceWifiStatus(blueteethBindController.currentDevice!, 1); - if (aa != null && aa is Map) { - wifiStatus = true; - updateDeviceBindStatus(blueteethBindController.currentDeviceMac!.value); - blueteethBindController.connect_wifi.value = aa; - } else { - wifiStatus = false; + try { + // 获取WiFi状态 + var aa = + await getDeviceWifiStatus(blueteethBindController.currentDevice!, 0); + if (aa != null && aa is Map) { + updateDeviceBindStatus(blueteethBindController.currentDeviceMac!.value); + wifiStatus = true; + blueteethBindController.connect_wifi.value = aa; + if (needSuccess) { + haveSuccess = true; + TopSlideNotification.show( + context, + text: "配网成功".tr, + textColor: themeController.currentColor.sc2, + ); + } + } else { + wifiStatus = false; + if (needSuccess) { + haveSuccess = true; + TopSlideNotification.show( + context, + text: "配网失败".tr, + textColor: themeController.currentColor.sc9, + ); + } + } + blueteethBindController.wifiStatus.value = wifiStatus ? 1 : 0; + } catch (e) { + print("获取WiFi状态异常: $e"); + blueteethBindController.wifiStatus.value = 0; } - blueteethBindController.wifiStatus.value = wifiStatus == true ? 1 : 0; + List wifiList = []; try { + // 获取WiFi列表 final result = await getWifiList(blueteethBindController.currentDevice!); + blueteethBindController.wifiConnectStatus.value = 1; + blueteethBindController.updateAll(); + if (result is List) { wifiList = result; } } catch (e) { - print("异常: $e"); + print("获取WiFi列表异常: $e"); + blueteethBindController.wifiConnectStatus.value = 0; + blueteethBindController.updateAll(); } - if (wifiList.length > 0) { + + if (_isDisposed) return; + + if (wifiList.isNotEmpty) { + // 成功获取WiFi列表 blueteethBindController.connectStatus.value = 1; blueteethBindController.updateAll(); - // Navigator.pop(context); - TopSlideNotification.show( - context, - text: "获取wifi列表成功".tr, - textColor: themeController.currentColor.sc2, - ); + // TopSlideNotification.show( + // context, + // text: "获取wifi列表成功".tr, + // textColor: themeController.currentColor.sc2, + // ); blueteethBindController.wifiList.value = wifiList; blueteethBindController.updateAll(); } else { - // Navigator.pop(context); + // 获取WiFi列表失败,尝试重新扫描 + if (retryCount < 2) { + // 最多重试2次 + print("第${retryCount + 1}次获取WiFi列表失败,尝试重新扫描..."); + + // TopSlideNotification.show( + // context, + // text: "正在重新扫描WiFi...".tr, + // textColor: themeController.currentColor.sc2, + // ); + + try { + // 发送关闭并重新打开WiFi扫描命令 + await sendCloseAndOpenWscanCommand( + blueteethBindController.currentDevice!); + + // 等待一段时间让设备重新扫描 + await Future.delayed(Duration(seconds: 3)); + + // 递归调用自身进行重试 + await initWifiStatusAndWifiList(retryCount: retryCount + 1); + return; // 重试后直接返回,避免执行后面的代码 + } catch (e) { + print("重新扫描WiFi失败: $e"); + // 继续执行下面的失败处理 + } + } + + // 重试次数用尽或重试失败,显示最终失败提示 TopSlideNotification.show( context, text: "获取wifi列表失败".tr, textColor: themeController.currentColor.sc9, ); + + // 可选:重置WiFi连接状态 + blueteethBindController.connectStatus.value = 0; + blueteethBindController.updateAll(); + } + + // 设置日志监听器 + if (!_isDisposed && blueteethBindController.currentDevice != null) { + lisObj = blueteethBindController.currentDevice!.statusStream + .listen((onData) async { + if (_isDisposed) return; + + if (onData.status == BleEventType.recvLineLog) { + final line = onData.val; + print("[bleee]: $line"); + edm.EasyDartModule.logger.info("[bleee]: $line"); + DailyLogUtils.writeLog("[bleee]: $line"); + } + // 添加连接状态监听 + if (onData.status == BleEventType.disconnected) { + print("蓝牙连接已断开"); + blueteethBindController.blueConnectFlag.value = 1; + blueteethBindController.updateAll(); + + // 显示断开提示 + // if (!_isDisposed) { + // TopSlideNotification.show( + // context, + // text: "蓝牙连接已断开,请重新连接".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } + _autoReconnect(); + } + }); } - lisObj = blueteethBindController.currentDevice!.statusStream - .listen((onData) async { - if (onData.status == BleEventType.recvLineLog) { - final line = onData.val; - print("[bleee]:" + line); - edm.EasyDartModule.logger.info("[bleee]:" + line); - DailyLogUtils.writeLog("[bleee]:" + line); - } - // if (onData.status == BleEventType.ready) { - // // showLoadingDialog(context, title: "获取wifi列表中...".tr); - // bool wifiStatus = false; - // var aa = await getDeviceWifiStatus( - // blueteethBindController.currentDevice!, 1); - // if (aa != null && aa is Map) { - // wifiStatus = true; - // blueteethBindController.connect_wifi.value = aa; - // } - // blueteethBindController.wifiStatus.value = wifiStatus == true ? 1 : 0; - // List wifiList = []; - // try { - // final result = - // await getWifiList(blueteethBindController.currentDevice!); - // if (result is List) { - // wifiList = result; - // } - // } catch (e) { - // print("异常: $e"); - // } - // if (wifiList.length > 0) { - // blueteethBindController.connectStatus.value = 1; - // blueteethBindController.updateAll(); - // // Navigator.pop(context); - // TopSlideNotification.show( - // context, - // text: "获取wifi列表成功".tr, - // textColor: themeController.currentColor.sc2, - // ); - // blueteethBindController.wifiList.value = wifiList; - // blueteethBindController.updateAll(); - // } else { - // Navigator.pop(context); - // TopSlideNotification.show( - // context, - // text: "获取wifi列表失败".tr, - // textColor: themeController.currentColor.sc9, - // ); - // } - // } - }); } initWifiList() async { @@ -1000,47 +1184,208 @@ class _WifiPageState extends State { } } - Future dealWifi(String mac) async { - final blueteethBindController = Get.find(); - final themeController = Get.find(); + // Future dealWifi(String mac) async { + // final blueteethBindController = Get.find(); + // final themeController = Get.find(); - // 显示加载对话框 - // showLoadingDialog(Get.context!, title: "连接中...".tr); + // // 显示加载对话框 + // // showLoadingDialog(Get.context!, title: "连接中...".tr); - // 设置超时定时器 - Timer? timeoutTimer; - bool isConnected = false; + // // 设置超时定时器 + // Timer? timeoutTimer; + // bool isConnected = false; + // try { + // // 开始扫描蓝牙设备 + // await FlutterBluePlus.startScan(timeout: Duration(seconds: 10)); + + // // 设置超时(20秒) + // timeoutTimer = Timer(Duration(seconds: 20), () { + // try { + // if (!isConnected) { + // blueteethBindController.blueConnectFlag.value = 1; + // blueteethBindController.updateAll(); + // // Navigator.of(context).pop(); // 先关闭 dialog + // WidgetsBinding.instance.addPostFrameCallback((_) { + // TopSlideNotification.show( + // context, + // text: "设备连接超时,请重试".tr, + // textColor: themeController.currentColor.sc9, + // ); + // }); + // FlutterBluePlus.stopScan(); + // } + // } catch (e) { + // print(e); + // } + // }); + + // // 监听扫描结果 + // StreamSubscription>? scanSubscription; + // scanSubscription = FlutterBluePlus.scanResults.listen((results) async { + // // 过滤出符合条件的设备 + // ScanResult? targetDevice; + + // for (var r in results) { + // if (r.advertisementData.manufacturerData.containsKey(0xFFED)) { + // List rawData = r.advertisementData.manufacturerData[0xFFED]!; + // BleDeviceData deviceData = parseBleData(rawData); + // String deviceMac = + // deviceData.deviceId.replaceAll(':', '').toLowerCase(); + // if (deviceMac == mac.toLowerCase()) { + // targetDevice = r; + // break; + // } + // } + // } + + // if (targetDevice != null && !isConnected) { + // isConnected = true; + // FlutterBluePlus.stopScan(); + // scanSubscription?.cancel(); + // timeoutTimer?.cancel(); + + // try { + // // 连接设备 + // // await targetDevice.device.connect(); + // THapp bledevice = THapp(device: targetDevice.device); + // await bledevice.device.connect(); + // var res2 = bledevice.isConnected; + // if (res2) { + // // Navigator.pop(context); + // blueteethBindController.blueConnectFlag.value = 2; + // TopSlideNotification.show( + // context, + // text: "蓝牙绑定.连接成功".tr, + // textColor: themeController.currentColor.sc2, + // ); + // blueteethBindController.currentDevice = bledevice; + // if (lisObj != null) { + // lisObj!.cancel(); + // } + // var aa; + // lisObj = blueteethBindController.currentDevice!.statusStream + // .listen((onData) async { + // if (onData.status == BleEventType.recvLineLog) { + // final line = onData.val; + // print("[bleee]:" + line); + // } + // if (onData.status == BleEventType.ready) { + // aa = await getDeviceNetVersion( + // blueteethBindController.currentDevice!, 0); + // if (aa == "4g") { + // // TopSlideNotification.show( + // // Get.context!, + // // text: "4g设备配置wifi提示".tr, + // // textColor: themeController.currentColor.sc9, + // // ); + // updateDeviceBindStatus( + // blueteethBindController.currentDeviceMac!.value); + // WidgetsBinding.instance.addPostFrameCallback((_) { + // TopSlideNotification.show( + // context, + // text: "4g设备配置wifi提示".tr, + // textColor: themeController.currentColor.sc9, + // ); + // Get.back(); + // }); + // return; + // } else if (aa == 'unknown') { + // blueteethBindController.netType.value = 3; + // blueteethBindController.updateAll(); + // WidgetsBinding.instance.addPostFrameCallback((_) { + // TopSlideNotification.show( + // context, + // text: "获取设备网络类型失败".tr, + // textColor: themeController.currentColor.sc9, + // ); + // }); + // } else { + // await initWifiStatusAndWifiList(); + // } + // } + // }); + // } else { + // // Navigator.pop(context); + // TopSlideNotification.show( + // context, + // text: "蓝牙绑定.连接失败".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } + // } catch (e) { + // // Navigator.of(Get.context!).pop(); // 关闭加载对话框 + // TopSlideNotification.show( + // Get.context!, + // text: "设备连接失败".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } + // } + // }); + + // // 等待扫描完成 + // await Future.delayed(Duration(seconds: 20)); + // } catch (e) { + // timeoutTimer?.cancel(); + // Navigator.of(Get.context!).pop(); // 关闭加载对话框 + // TopSlideNotification.show( + // Get.context!, + // text: "扫描过程中发生错误".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } finally { + // timeoutTimer?.cancel(); + // await FlutterBluePlus.stopScan(); + // } + // } + + Future dealWifi(String mac, + {bool needTip = true, bool needSuccess = false}) async { + if (dealing) { + return; + } try { - // 开始扫描蓝牙设备 + dealing = true; + if (_isDisposed) return; + + final blueteethBindController = Get.find(); + final themeController = Get.find(); + + // 清理之前的资源 + await _cleanupResources(); + // 检查蓝牙状态 + var bluetoothState = await FlutterBluePlus.isOn; + blueteethBindController.bluetoothStatus.value = bluetoothState; + blueteethBindController.updateAll(); + if (!bluetoothState) { + await _showBluetoothNotEnabledDialog(); + return; + } + + // 开始扫描 await FlutterBluePlus.startScan(timeout: Duration(seconds: 10)); - // 设置超时(20秒) - timeoutTimer = Timer(Duration(seconds: 20), () { - try { - if (!isConnected) { - blueteethBindController.blueConnectFlag.value = 1; - blueteethBindController.updateAll(); - // Navigator.of(context).pop(); // 先关闭 dialog - WidgetsBinding.instance.addPostFrameCallback((_) { - TopSlideNotification.show( - context, - text: "设备连接超时,请重试".tr, - textColor: themeController.currentColor.sc9, - ); - }); - FlutterBluePlus.stopScan(); - } - } catch (e) { - print(e); + bool isConnected = false; + + // 设置超时定时器 + _timeoutTimer = Timer(Duration(seconds: 20), () { + if (!isConnected && !_isDisposed) { + blueteethBindController.blueConnectFlag.value = 1; + blueteethBindController.netType.value = 3; + TopSlideNotification.show( + context, + text: "设备连接超时,请点击刷新重试".tr, + textColor: themeController.currentColor.sc9, + ); + FlutterBluePlus.stopScan(); + blueteethBindController.updateAll(); } }); // 监听扫描结果 - StreamSubscription>? scanSubscription; - scanSubscription = FlutterBluePlus.scanResults.listen((results) async { - // 过滤出符合条件的设备 - ScanResult? targetDevice; + _scanSubscription = FlutterBluePlus.scanResults.listen((results) async { + if (_isDisposed) return; for (var r in results) { if (r.advertisementData.manufacturerData.containsKey(0xFFED)) { @@ -1057,102 +1402,143 @@ class _WifiPageState extends State { if (targetDevice != null && !isConnected) { isConnected = true; - FlutterBluePlus.stopScan(); - scanSubscription?.cancel(); - timeoutTimer?.cancel(); + _timeoutTimer?.cancel(); + _scanSubscription?.cancel(); + var streamlog; try { - // 连接设备 - // await targetDevice.device.connect(); - THapp bledevice = THapp(device: targetDevice.device); - await bledevice.device.connect(); - var res2 = bledevice.isConnected; - if (res2) { - // Navigator.pop(context); - blueteethBindController.blueConnectFlag.value = 2; - TopSlideNotification.show( - context, - text: "蓝牙绑定.连接成功".tr, - textColor: themeController.currentColor.sc2, - ); - blueteethBindController.currentDevice = bledevice; - if (lisObj != null) { - lisObj!.cancel(); + THapp bledevice = THapp(device: targetDevice!.device); + blueteethBindController.currentDevice = bledevice; + streamlog = blueteethBindController.currentDevice!.logingStream + .listen((log) { + ef.log("[传感器设备日志]: $log"); + edm.EasyDartModule.logger.info("[传感器设备日志]: $log"); + }); + bledevice.autoConnect = true; + + const int maxRetry = 3; + int retryCount = 0; + bool connected = false; + + while (retryCount < maxRetry && !_isDisposed) { + try { + await bledevice.device.connect(); + connected = bledevice.isConnected; + if (connected) break; + } catch (e) { + retryCount++; + print("[蓝牙连接错误] 第${retryCount}次重试: $e"); + edm.EasyDartModule.logger + .info("[蓝牙连接错误] 第${retryCount}次重试: $e"); + DailyLogUtils.writeLog("[蓝牙连接错误] 第${retryCount}次重试: $e"); + + // 延迟重试 + await Future.delayed(const Duration(seconds: 2)); } - var aa; - lisObj = blueteethBindController.currentDevice!.statusStream - .listen((onData) async { + } + + if (connected && !_isDisposed) { + blueteethBindController.blueConnectFlag.value = 2; + + // TopSlideNotification.show( + // context, + // text: "蓝牙连接成功".tr, + // textColor: themeController.currentColor.sc2, + // ); + + lisObj = bledevice.statusStream.listen((onData) async { + if (_isDisposed) return; + if (onData.status == BleEventType.recvLineLog) { final line = onData.val; - print("[bleee]:" + line); + print("[bleee]: $line"); + edm.EasyDartModule.logger.info("[bleee]: $line"); + DailyLogUtils.writeLog("[bleee]: $line"); } + + // 添加连接状态监听 + if (onData.status == BleEventType.disconnected) { + print("蓝牙连接已断开"); + blueteethBindController.blueConnectFlag.value = 1; + blueteethBindController.updateAll(); + + // 显示断开提示 + // if (!_isDisposed) { + // TopSlideNotification.show( + // context, + // text: "蓝牙连接已断开,请重新连接".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } + _autoReconnect(); + } + if (onData.status == BleEventType.ready) { - aa = await getDeviceNetVersion( - blueteethBindController.currentDevice!, 0); + var aa = await getDeviceNetVersion(bledevice, 0); if (aa == "4g") { - // TopSlideNotification.show( - // Get.context!, - // text: "4g设备配置wifi提示".tr, - // textColor: themeController.currentColor.sc9, - // ); + blueteethBindController.netType.value = 2; + blueteethBindController.updateAll(); updateDeviceBindStatus( blueteethBindController.currentDeviceMac!.value); - WidgetsBinding.instance.addPostFrameCallback((_) { - TopSlideNotification.show( + await showTipDialog( context, - text: "4g设备配置wifi提示".tr, - textColor: themeController.currentColor.sc9, - ); - Get.back(); - }); - return; + Text( + "4g设备配置wifi提示".tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: AppConstants().title_text_fontSize), + )); } else if (aa == 'unknown') { blueteethBindController.netType.value = 3; blueteethBindController.updateAll(); - WidgetsBinding.instance.addPostFrameCallback((_) { - TopSlideNotification.show( - context, - text: "获取设备网络类型失败".tr, - textColor: themeController.currentColor.sc9, - ); - }); + TopSlideNotification.show( + context, + text: "获取设备网络类型失败".tr, + textColor: themeController.currentColor.sc9, + ); } else { - await initWifiStatusAndWifiList(); + blueteethBindController.netType.value = 1; + blueteethBindController.updateAll(); + blueteethBindController.wifiConnectStatus.value = 0; + blueteethBindController.updateAll(); + await initWifiStatusAndWifiList(needSuccess: needSuccess); } + dealing = false; } }); } else { - // Navigator.pop(context); TopSlideNotification.show( context, - text: "蓝牙绑定.连接失败".tr, + text: "设备连接失败,请点击刷新重试".tr, textColor: themeController.currentColor.sc9, ); } } catch (e) { - // Navigator.of(Get.context!).pop(); // 关闭加载对话框 + blueteethBindController.blueConnectFlag.value = 1; TopSlideNotification.show( - Get.context!, - text: "设备连接失败".tr, + context, + text: "设备连接失败,请点击刷新重试".tr, textColor: themeController.currentColor.sc9, ); + } finally { + streamlog.close(); } } }); - - // 等待扫描完成 await Future.delayed(Duration(seconds: 20)); } catch (e) { - timeoutTimer?.cancel(); - Navigator.of(Get.context!).pop(); // 关闭加载对话框 - TopSlideNotification.show( - Get.context!, - text: "扫描过程中发生错误".tr, - textColor: themeController.currentColor.sc9, - ); + if (!_isDisposed) { + TopSlideNotification.show( + context, + text: "扫描过程中发生错误".tr, + textColor: themeController.currentColor.sc9, + ); + dealing = false; + } } finally { - timeoutTimer?.cancel(); + _timeoutTimer?.cancel(); await FlutterBluePlus.stopScan(); + dealing = false; } } @@ -1189,4 +1575,119 @@ class _WifiPageState extends State { onFailure: (res) {}, ); } + + Future restoreWifi(THapp tHapp, aa) async { + if (aa != null && aa is Map) { + return aa; + } + await sendCloseAndOpenWscanCommand(tHapp); + return await getDeviceWifiStatus(blueteethBindController.currentDevice!, 4, + link: true); + } + + Future rebootDeviceCurrent(THapp tHapp, aa) async { + if (aa != null && aa is Map) { + return aa; + } + blueteethBindController.connect_wifi.value = {}; + await rebootDevice( + blueteethBindController.currentDevice!, + ); + await dealWifi( + blueteethBindController.currentDeviceMac.value, + ); + return await getDeviceWifiStatus(blueteethBindController.currentDevice!, 1, + link: true); + } + + Future _cleanupResources() async { + // 取消监听器 + lisObj?.cancel(); + lisObj = null; + + // 取消定时器 + _timeoutTimer?.cancel(); + _timeoutTimer = null; + + // 取消扫描订阅 + _scanSubscription?.cancel(); + _scanSubscription = null; + + await _disconnectDevice(); + // 停止扫描 + FlutterBluePlus.stopScan(); + } + + Future _disconnectDevice() async { + try { + if (blueteethBindController.currentDevice != null) { + await blueteethBindController.currentDevice!.disconnect(); + // blueteethBindController.currentDevice = null; + } + DailyLogUtils.writeLog("关闭蓝牙连接成功".tr); + } catch (e) { + DailyLogUtils.writeError("关闭蓝牙连接失败: $e"); + } finally { + // dealing = false; + } + } + + _showBluetoothNotEnabledDialog() async { + await showTipDialog( + context, + Column( + children: [ + Text( + "蓝牙未开启".tr, + style: TextStyle( + fontSize: AppConstants().title_text_fontSize, + color: themeController.currentColor.sc3), + ), + SizedBox( + height: 20.rpx, + ), + Text( + "请先打开蓝牙在进行设备扫描".tr, + style: TextStyle( + fontSize: AppConstants().normal_text_fontSize, + color: themeController.currentColor.sc3), + ), + ], + )); + } + + int _retryCount = 0; + final int _maxRetry = 3; + Future _autoReconnect() async { + if (blueteethBindController.currentDevice == null) return; + + while (_retryCount < _maxRetry) { + try { + print("[蓝牙重连] 第${_retryCount + 1}次尝试连接..."); + await blueteethBindController.currentDevice!.connect(); + + // 检查是否连接成功 + var isConnected = + await blueteethBindController.currentDevice!.isConnected; + if (isConnected) { + print("[蓝牙重连] 连接成功 ✅"); + _retryCount = 0; // 重置重试次数 + return; + } else { + throw Exception("连接失败"); + } + } catch (e) { + _retryCount++; + print("[蓝牙重连] 第$_retryCount次连接失败:$e"); + + if (_retryCount >= _maxRetry) { + print("[蓝牙重连] 已达到最大重试次数($_maxRetry),停止重连 ❌"); + break; + } + + // 等待 2 秒后重试 + await Future.delayed(Duration(seconds: 2)); + } + } + } } diff --git a/lib/pages/device_bind/wifi_page_person.dart b/lib/pages/device_bind/wifi_page_person.dart index 41a4187..754ff29 100644 --- a/lib/pages/device_bind/wifi_page_person.dart +++ b/lib/pages/device_bind/wifi_page_person.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:EasyDartModule/EasyDartModule.dart' as edm; import 'package:easydevice/easydevice.dart'; import 'package:ef/ef.dart'; import 'package:flutter/material.dart'; @@ -24,7 +25,6 @@ import 'package:vbvs_app/controller/user_info_controller.dart'; import 'package:vbvs_app/model/BleDeviceData.dart'; import 'package:vbvs_app/pages/device_bind/blueteeth_device_page.dart'; import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart'; -import 'package:EasyDartModule/EasyDartModule.dart' as edm; class WifiPagePerson extends StatefulWidget { var type; @@ -41,6 +41,14 @@ class _WifiPagePersonState extends State { PersonController personController = Get.find(); ThemeController themeController = Get.find(); var lisObj; + //new + bool haveSuccess = false; + bool dealing = false; //是否正在刷新 + bool _isDisposed = false; + Timer? _timeoutTimer; + StreamSubscription>? _scanSubscription; + ScanResult? targetDevice; + DateTime _lastTapTime = DateTime.now(); @override void initState() { @@ -60,13 +68,21 @@ class _WifiPagePersonState extends State { }); } + // @override + // void dispose() { + // super.dispose(); + // if (lisObj != null) { + // lisObj.cancel(); + // } + // blueteethBindController.currentDevice!.disconnect(); + // } + @override void dispose() { + _isDisposed = true; + _cleanupResources(); + // _disconnectDevice(); super.dispose(); - if (lisObj != null) { - lisObj.cancel(); - } - blueteethBindController.currentDevice!.disconnect(); } @override @@ -510,8 +526,7 @@ class _WifiPagePersonState extends State { wifiItem['ssid'] ?? '未命名'.tr, onConfirm: () async { - // showLoadingDialog( - // context); // 显示 loading + haveSuccess = false; blueteethBindController .selectWifi .value = wifiItem; @@ -522,22 +537,46 @@ class _WifiPagePersonState extends State { blueteethBindController .currentDevice!); // Navigator.pop(context); + bool memoryFlag = + await queryMemory( + blueteethBindController + .currentDevice!); + if (!memoryFlag) { + await rebootDevice( + blueteethBindController + .currentDevice!); + await dealWifi( + widget.type['mac'], + needSuccess: true); + // return; + } + if (!flag) { + blueteethBindController + .selectWifi + .value = {}; + return; + } if (flag) { - // bool wifiStatus = - // await getWifiStatus( - // blueteethBindController - // .currentDevice!); - bool wifiStatus = false; var aa = await getDeviceWifiStatus( blueteethBindController .currentDevice!, - 1); + 1, + link: true); + aa = await restoreWifi( + blueteethBindController + .currentDevice!, + aa); + if (!memoryFlag) { + aa = await rebootDeviceCurrent( + blueteethBindController + .currentDevice!, + aa); + } blueteethBindController .selectWifi .value = {}; if (aa != null && aa is Map) { - wifiStatus = true; blueteethBindController .connect_wifi .value = aa; @@ -545,72 +584,76 @@ class _WifiPagePersonState extends State { blueteethBindController .connect_wifi .value = {}; + blueteethBindController + .wifiStatus + .value == + 0; } blueteethBindController .wifiStatus .value = - wifiStatus == true + (aa != null && + aa != false) ? 1 : 0; - blueteethBindController - .wifiStatus - .value = - wifiStatus == true - ? 1 - : 0; - if (wifiStatus) { + if (aa != null && + aa != false) { updateDeviceBindStatus( blueteethBindController .currentDeviceMac! .value); - // Navigator.pop(context); - TopSlideNotification - .show( - context, - text: "wifi页.配网成功".tr, - textColor: - themeController - .currentColor - .sc2, - ); + if (!haveSuccess) { + TopSlideNotification + .show( + context, + text: "配网成功".tr, + textColor: + themeController + .currentColor + .sc2, + ); + } + } else { + //读取不到wifi信息 + blueteethBindController + .connect_wifi + .value = {}; + blueteethBindController + .selectWifi + .value = {}; blueteethBindController .wifiStatus - .value = 1; - blueteethBindController - .updateAll(); - } else { - // Navigator.pop(context); + .value = 0; TopSlideNotification .show( context, - text: "wifi页.配网失败".tr, + text: "配网失败".tr, textColor: themeController .currentColor .sc9, ); - blueteethBindController - .wifiStatus - .value = 0; - blueteethBindController - .updateAll(); } } else { - // Navigator.pop(context); + blueteethBindController + .connect_wifi + .value = {}; + blueteethBindController + .selectWifi + .value = {}; + blueteethBindController + .wifiStatus.value = 0; TopSlideNotification.show( context, - text: "wifi页.配网失败".tr, + text: "配网失败".tr, textColor: themeController .currentColor .sc9, ); - blueteethBindController - .wifiStatus.value = 0; - blueteethBindController - .updateAll(); } }); + }, child: Row( mainAxisSize: @@ -663,8 +706,97 @@ class _WifiPagePersonState extends State { padding: EdgeInsets.symmetric( horizontal: 20.rpx, vertical: 10.rpx), borderRadius: 20.rpx, + // onTap: () async { + // //检测蓝牙开关 + // var bluetoothState = + // await FlutterBluePlus.isOn; + // blueteethBindController.bluetoothStatus + // .value = bluetoothState; + // if (!bluetoothState) { + // await _showBluetoothNotEnabledDialog(); + // return; + // } + // //检测蓝牙连接 + // if (blueteethBindController + // .blueConnectFlag.value == + // 0 || + // blueteethBindController + // .blueConnectFlag.value == + // 1) { + // blueteethBindController + // .blueConnectFlag.value = 0; + // dealWifi(widget.type['mac']).then((aa) { + // print("object"); + // }); + // return; + // } + + // if (blueteethBindController + // .netType.value == + // 0) { + // return; + // } + // blueteethBindController.netType.value = 0; + // blueteethBindController.updateAll(); + // var aa = await getDeviceNetVersion( + // blueteethBindController + // .currentDevice!, + // 1); + // if (aa == "4g") { + // updateDeviceBindStatus( + // blueteethBindController + // .currentDeviceMac!.value); + // TopSlideNotification.show( + // context, + // text: "4g设备配置wifi提示".tr, + // textColor: + // themeController.currentColor.sc2, + // ); + // blueteethBindController.netType.value = + // 2; + // blueteethBindController + // .connectStatus.value = 1; + // blueteethBindController.updateAll(); + // Future.delayed( + // const Duration(seconds: 1), () { + // Get.toNamed("/calibrationPage", + // arguments: 1); + // }); + // } else if (aa == 'unknown') { + // blueteethBindController.netType.value = + // 3; + // blueteethBindController.updateAll(); + // WidgetsBinding.instance + // .addPostFrameCallback((_) { + // TopSlideNotification.show( + // context, + // text: "获取设备网络类型失败".tr, + // textColor: themeController + // .currentColor.sc9, + // ); + // }); + // } else { + // blueteethBindController.netType.value = + // 1; + // blueteethBindController.updateAll(); + // await initWifiStatusAndWifiList(); + // } + // }, onTap: () async { - //检测蓝牙开关 + // if (dealing) { + // return; + // } + if (DateTime.now() + .difference(_lastTapTime) < + Duration(seconds: 2)) { + return; // 防止快速重复点击 + } + _lastTapTime = DateTime.now(); + blueteethBindController.netType.value = 0; + blueteethBindController + .blueConnectFlag.value = 0; + blueteethBindController.wifiList.value = + []; var bluetoothState = await FlutterBluePlus.isOn; blueteethBindController.bluetoothStatus @@ -673,7 +805,6 @@ class _WifiPagePersonState extends State { await _showBluetoothNotEnabledDialog(); return; } - //检测蓝牙连接 if (blueteethBindController .blueConnectFlag.value == 0 || @@ -682,9 +813,15 @@ class _WifiPagePersonState extends State { 1) { blueteethBindController .blueConnectFlag.value = 0; - dealWifi(widget.type['mac']).then((aa) { - print("object"); - }); + try { + await dealWifi(widget.type['mac'], + needTip: false) + .then((aa) { + print("object"); + }); + } catch (e) { + ef.log("$e"); + } return; } @@ -716,6 +853,7 @@ class _WifiPagePersonState extends State { blueteethBindController.updateAll(); Future.delayed( const Duration(seconds: 1), () { + _cleanupResources(); Get.toNamed("/calibrationPage", arguments: 1); }); @@ -732,6 +870,7 @@ class _WifiPagePersonState extends State { .currentColor.sc9, ); }); + await initWifiStatusAndWifiList(); } else { blueteethBindController.netType.value = 1; @@ -784,63 +923,209 @@ class _WifiPagePersonState extends State { ); } - Future initWifiStatusAndWifiList() async { + // Future initWifiStatusAndWifiList() async { + // if (lisObj != null) { + // lisObj!.cancel(); + // } + // bool wifiStatus = false; + // var aa = + // await getDeviceWifiStatus(blueteethBindController.currentDevice!, 0); + // if (aa != null && aa is Map) { + // updateDeviceBindStatus(blueteethBindController.currentDeviceMac!.value); + // wifiStatus = true; + // blueteethBindController.connect_wifi.value = aa; + // } else { + // wifiStatus = false; + // } + // blueteethBindController.wifiStatus.value = wifiStatus == true ? 1 : 0; + // List wifiList = []; + // try { + // final result = await getWifiList(blueteethBindController.currentDevice!); + // blueteethBindController.wifiConnectStatus.value = 1; + // blueteethBindController.updateAll(); + // if (result is List) { + // wifiList = result; + // } + // } catch (e) { + // print("异常: $e"); + // blueteethBindController.wifiConnectStatus.value = 0; + // blueteethBindController.updateAll(); + // } + // if (wifiList.length > 0) { + // blueteethBindController.connectStatus.value = 1; + // blueteethBindController.updateAll(); + // // Navigator.pop(context); + // TopSlideNotification.show( + // context, + // text: "获取wifi列表成功".tr, + // textColor: themeController.currentColor.sc2, + // ); + // blueteethBindController.wifiList.value = wifiList; + // blueteethBindController.updateAll(); + // } else { + // // Navigator.pop(context); + + // TopSlideNotification.show( + // context, + // text: "获取wifi列表失败".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } + // lisObj = blueteethBindController.currentDevice!.statusStream + // .listen((onData) async { + // if (onData.status == BleEventType.recvLineLog) { + // final line = onData.val; + // print("[bleee]:" + line); + // edm.EasyDartModule.logger.info("[bleee]:" + line); + // DailyLogUtils.writeLog("[bleee]:" + line); + // } + // }); + // } + + Future initWifiStatusAndWifiList( + {int retryCount = 0, bool needSuccess = false}) async { + if (_isDisposed) return; + + // 取消之前的监听器 if (lisObj != null) { lisObj!.cancel(); + lisObj = null; } + bool wifiStatus = false; - var aa = - await getDeviceWifiStatus(blueteethBindController.currentDevice!, 0); - if (aa != null && aa is Map) { - updateDeviceBindStatus(blueteethBindController.currentDeviceMac!.value); - wifiStatus = true; - blueteethBindController.connect_wifi.value = aa; - } else { - wifiStatus = false; + try { + // 获取WiFi状态 + var aa = + await getDeviceWifiStatus(blueteethBindController.currentDevice!, 0); + if (aa != null && aa is Map) { + updateDeviceBindStatus(blueteethBindController.currentDeviceMac!.value); + wifiStatus = true; + blueteethBindController.connect_wifi.value = aa; + if (needSuccess) { + haveSuccess = true; + TopSlideNotification.show( + context, + text: "配网成功".tr, + textColor: themeController.currentColor.sc2, + ); + } + } else { + wifiStatus = false; + if (needSuccess) { + haveSuccess = true; + TopSlideNotification.show( + context, + text: "配网失败".tr, + textColor: themeController.currentColor.sc9, + ); + } + } + blueteethBindController.wifiStatus.value = wifiStatus ? 1 : 0; + } catch (e) { + print("获取WiFi状态异常: $e"); + blueteethBindController.wifiStatus.value = 0; } - blueteethBindController.wifiStatus.value = wifiStatus == true ? 1 : 0; + List wifiList = []; try { + // 获取WiFi列表 final result = await getWifiList(blueteethBindController.currentDevice!); blueteethBindController.wifiConnectStatus.value = 1; blueteethBindController.updateAll(); + if (result is List) { wifiList = result; } } catch (e) { - print("异常: $e"); + print("获取WiFi列表异常: $e"); blueteethBindController.wifiConnectStatus.value = 0; blueteethBindController.updateAll(); } - if (wifiList.length > 0) { + + if (_isDisposed) return; + + if (wifiList.isNotEmpty) { + // 成功获取WiFi列表 blueteethBindController.connectStatus.value = 1; blueteethBindController.updateAll(); - // Navigator.pop(context); - TopSlideNotification.show( - context, - text: "获取wifi列表成功".tr, - textColor: themeController.currentColor.sc2, - ); + // TopSlideNotification.show( + // context, + // text: "获取wifi列表成功".tr, + // textColor: themeController.currentColor.sc2, + // ); blueteethBindController.wifiList.value = wifiList; blueteethBindController.updateAll(); } else { - // Navigator.pop(context); + // 获取WiFi列表失败,尝试重新扫描 + if (retryCount < 2) { + // 最多重试2次 + print("第${retryCount + 1}次获取WiFi列表失败,尝试重新扫描..."); + // TopSlideNotification.show( + // context, + // text: "正在重新扫描WiFi...".tr, + // textColor: themeController.currentColor.sc2, + // ); + + try { + // 发送关闭并重新打开WiFi扫描命令 + await sendCloseAndOpenWscanCommand( + blueteethBindController.currentDevice!); + + // 等待一段时间让设备重新扫描 + await Future.delayed(Duration(seconds: 3)); + + // 递归调用自身进行重试 + await initWifiStatusAndWifiList(retryCount: retryCount + 1); + return; // 重试后直接返回,避免执行后面的代码 + } catch (e) { + print("重新扫描WiFi失败: $e"); + // 继续执行下面的失败处理 + } + } + + // 重试次数用尽或重试失败,显示最终失败提示 TopSlideNotification.show( context, text: "获取wifi列表失败".tr, textColor: themeController.currentColor.sc9, ); + + // 可选:重置WiFi连接状态 + blueteethBindController.connectStatus.value = 0; + blueteethBindController.updateAll(); + } + + // 设置日志监听器 + if (!_isDisposed && blueteethBindController.currentDevice != null) { + lisObj = blueteethBindController.currentDevice!.statusStream + .listen((onData) async { + if (_isDisposed) return; + + if (onData.status == BleEventType.recvLineLog) { + final line = onData.val; + print("[bleee]: $line"); + edm.EasyDartModule.logger.info("[bleee]: $line"); + DailyLogUtils.writeLog("[bleee]: $line"); + } + // 添加连接状态监听 + if (onData.status == BleEventType.disconnected) { + print("蓝牙连接已断开"); + blueteethBindController.blueConnectFlag.value = 1; + blueteethBindController.updateAll(); + + // 显示断开提示 + // if (!_isDisposed) { + // TopSlideNotification.show( + // context, + // text: "蓝牙连接已断开,请重新连接".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } + _autoReconnect(); + } + }); } - lisObj = blueteethBindController.currentDevice!.statusStream - .listen((onData) async { - if (onData.status == BleEventType.recvLineLog) { - final line = onData.val; - print("[bleee]:" + line); - edm.EasyDartModule.logger.info("[bleee]:" + line); - DailyLogUtils.writeLog("[bleee]:" + line); - } - }); } getWifiIconByRsso(wifiItem) { @@ -892,19 +1177,211 @@ class _WifiPagePersonState extends State { } } - Future dealWifi(String mac) async { - final blueteethBindController = Get.find(); - final themeController = Get.find(); + // Future dealWifi(String mac, + // {bool needTip = true, bool needSuccess = false}) async { + // final blueteethBindController = Get.find(); + // final themeController = Get.find(); - // 显示加载对话框 - // showLoadingDialog(Get.context!, title: "连接中...".tr); + // // 显示加载对话框 + // // showLoadingDialog(Get.context!, title: "连接中...".tr); - // 设置超时定时器 - Timer? timeoutTimer; - bool isConnected = false; + // // 设置超时定时器 + // Timer? timeoutTimer; + // bool isConnected = false; + // try { + // // 开始扫描蓝牙设备 + // var bluetoothState = await FlutterBluePlus.isOn; + // blueteethBindController.bluetoothStatus.value = bluetoothState; + // blueteethBindController.updateAll(); + // if (!bluetoothState) { + // await _showBluetoothNotEnabledDialog(); + // return; + // } + // await FlutterBluePlus.startScan(timeout: Duration(seconds: 10)); + + // // 设置超时(20秒) + // timeoutTimer = Timer(Duration(seconds: 20), () { + // try { + // if (!isConnected) { + // blueteethBindController.blueConnectFlag.value = 1; + // blueteethBindController.updateAll(); + // // Navigator.of(context).pop(); // 先关闭 dialog + // WidgetsBinding.instance.addPostFrameCallback((_) { + // TopSlideNotification.show( + // context, + // text: "设备连接超时,请重试".tr, + // textColor: themeController.currentColor.sc9, + // ); + // }); + // FlutterBluePlus.stopScan(); + // } + // } catch (e) { + // print(e); + // } + // }); + + // // 监听扫描结果 + // StreamSubscription>? scanSubscription; + // scanSubscription = FlutterBluePlus.scanResults.listen((results) async { + // // 过滤出符合条件的设备 + // ScanResult? targetDevice; + + // for (var r in results) { + // if (r.advertisementData.manufacturerData.containsKey(0xFFED)) { + // List rawData = r.advertisementData.manufacturerData[0xFFED]!; + // BleDeviceData deviceData = parseBleData(rawData); + // String deviceMac = + // deviceData.deviceId.replaceAll(':', '').toLowerCase(); + // if (deviceMac == mac.toLowerCase()) { + // targetDevice = r; + // break; + // } + // } + // } + + // if (targetDevice != null && !isConnected) { + // isConnected = true; + // FlutterBluePlus.stopScan(); + // scanSubscription?.cancel(); + // timeoutTimer?.cancel(); + + // try { + // // 连接设备 + // // await targetDevice.device.connect(); + // THapp bledevice = THapp(device: targetDevice.device); + // await bledevice.device.connect(); + // var res2 = bledevice.isConnected; + // if (res2) { + // // Navigator.pop(context); + // blueteethBindController.blueConnectFlag.value = 2; + // TopSlideNotification.show( + // context, + // text: "蓝牙绑定.连接成功".tr, + // textColor: themeController.currentColor.sc2, + // ); + // blueteethBindController.currentDevice = bledevice; + // if (lisObj != null) { + // lisObj!.cancel(); + // } + // var aa; + // lisObj = blueteethBindController.currentDevice!.statusStream + // .listen((onData) async { + // if (onData.status == BleEventType.recvLineLog) { + // final line = onData.val; + // print("[bleee]:" + line); + // edm.EasyDartModule.logger.info("[bleee]:" + line); + // DailyLogUtils.writeLog("[bleee]:" + line); + // } + // if (onData.status == BleEventType.ready) { + // aa = await getDeviceNetVersion( + // blueteethBindController.currentDevice!, 0); + // if (aa == "4g") { + // // TopSlideNotification.show( + // // Get.context!, + // // text: "4g设备配置wifi提示".tr, + // // textColor: themeController.currentColor.sc9, + // // ); + // blueteethBindController.netType.value = 2; + // blueteethBindController.updateAll(); + // updateDeviceBindStatus( + // blueteethBindController.currentDeviceMac!.value); + // // WidgetsBinding.instance.addPostFrameCallback((_) { + // // TopSlideNotification.show( + // // context, + // // text: "4g设备配置wifi提示".tr, + // // textColor: themeController.currentColor.sc9, + // // ); + // // // Get.back(); + // // }); + // await showTipDialog( + // context, + // Text( + // "4g设备配置wifi提示".tr, + // style: TextStyle( + // color: themeController.currentColor.sc3, + // fontSize: AppConstants().title_text_fontSize), + // )); + // // return; + // } else if (aa == 'unknown') { + // blueteethBindController.netType.value = 3; + // blueteethBindController.updateAll(); + // blueteethBindController.updateAll(); + // WidgetsBinding.instance.addPostFrameCallback((_) { + // TopSlideNotification.show( + // context, + // text: "获取设备网络类型失败".tr, + // textColor: themeController.currentColor.sc9, + // ); + // }); + // } else { + // blueteethBindController.netType.value = 1; + // blueteethBindController.updateAll(); + // // TopSlideNotification.show( + // // context, + // // text: "当前设备为wifi网络设备".tr, + // // textColor: themeController.currentColor.sc9, + // // ); + // blueteethBindController.wifiConnectStatus.value = 0; + // blueteethBindController.updateAll(); + // await initWifiStatusAndWifiList(); + // } + // } + // }); + // } else { + // // Navigator.pop(context); + // TopSlideNotification.show( + // context, + // text: "蓝牙绑定.连接失败".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } + // } catch (e) { + // // Navigator.of(Get.context!).pop(); // 关闭加载对话框 + // // TopSlideNotification.show( + // // Get.context!, + // // text: "设备连接失败".tr, + // // textColor: themeController.currentColor.sc9, + // // ); + // NewTopSlideNotification.show( + // text: "设备连接失败".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } + // } + // }); + + // // 等待扫描完成 + // await Future.delayed(Duration(seconds: 20)); + // } catch (e) { + // timeoutTimer?.cancel(); + // Navigator.of(Get.context!).pop(); // 关闭加载对话框 + // TopSlideNotification.show( + // Get.context!, + // text: "扫描过程中发生错误".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } finally { + // timeoutTimer?.cancel(); + // await FlutterBluePlus.stopScan(); + // } + // } + + Future dealWifi(String mac, + {bool needTip = true, bool needSuccess = false}) async { + if (dealing) { + return; + } try { - // 开始扫描蓝牙设备 + dealing = true; + if (_isDisposed) return; + + final blueteethBindController = Get.find(); + final themeController = Get.find(); + + // 清理之前的资源 + await _cleanupResources(); + // 检查蓝牙状态 var bluetoothState = await FlutterBluePlus.isOn; blueteethBindController.bluetoothStatus.value = bluetoothState; blueteethBindController.updateAll(); @@ -912,34 +1389,30 @@ class _WifiPagePersonState extends State { await _showBluetoothNotEnabledDialog(); return; } + + // 开始扫描 await FlutterBluePlus.startScan(timeout: Duration(seconds: 10)); - // 设置超时(20秒) - timeoutTimer = Timer(Duration(seconds: 20), () { - try { - if (!isConnected) { - blueteethBindController.blueConnectFlag.value = 1; - blueteethBindController.updateAll(); - // Navigator.of(context).pop(); // 先关闭 dialog - WidgetsBinding.instance.addPostFrameCallback((_) { - TopSlideNotification.show( - context, - text: "设备连接超时,请重试".tr, - textColor: themeController.currentColor.sc9, - ); - }); - FlutterBluePlus.stopScan(); - } - } catch (e) { - print(e); + bool isConnected = false; + + // 设置超时定时器 + _timeoutTimer = Timer(Duration(seconds: 20), () { + if (!isConnected && !_isDisposed) { + blueteethBindController.blueConnectFlag.value = 1; + blueteethBindController.netType.value = 3; + TopSlideNotification.show( + context, + text: "设备连接超时,请点击刷新重试".tr, + textColor: themeController.currentColor.sc9, + ); + FlutterBluePlus.stopScan(); + blueteethBindController.updateAll(); } }); // 监听扫描结果 - StreamSubscription>? scanSubscription; - scanSubscription = FlutterBluePlus.scanResults.listen((results) async { - // 过滤出符合条件的设备 - ScanResult? targetDevice; + _scanSubscription = FlutterBluePlus.scanResults.listen((results) async { + if (_isDisposed) return; for (var r in results) { if (r.advertisementData.manufacturerData.containsKey(0xFFED)) { @@ -956,58 +1429,84 @@ class _WifiPagePersonState extends State { if (targetDevice != null && !isConnected) { isConnected = true; - FlutterBluePlus.stopScan(); - scanSubscription?.cancel(); - timeoutTimer?.cancel(); + _timeoutTimer?.cancel(); + _scanSubscription?.cancel(); + var streamlog; try { - // 连接设备 - // await targetDevice.device.connect(); - THapp bledevice = THapp(device: targetDevice.device); - await bledevice.device.connect(); - var res2 = bledevice.isConnected; - if (res2) { - // Navigator.pop(context); - blueteethBindController.blueConnectFlag.value = 2; - TopSlideNotification.show( - context, - text: "蓝牙绑定.连接成功".tr, - textColor: themeController.currentColor.sc2, - ); - blueteethBindController.currentDevice = bledevice; - if (lisObj != null) { - lisObj!.cancel(); + THapp bledevice = THapp(device: targetDevice!.device); + blueteethBindController.currentDevice = bledevice; + streamlog = blueteethBindController.currentDevice!.logingStream + .listen((log) { + ef.log("[传感器设备日志]: $log"); + edm.EasyDartModule.logger.info("[传感器设备日志]: $log"); + }); + bledevice.autoConnect = true; + + const int maxRetry = 3; + int retryCount = 0; + bool connected = false; + + while (retryCount < maxRetry && !_isDisposed) { + try { + await bledevice.device.connect(); + connected = bledevice.isConnected; + if (connected) break; + } catch (e) { + retryCount++; + print("[蓝牙连接错误] 第${retryCount}次重试: $e"); + edm.EasyDartModule.logger + .info("[蓝牙连接错误] 第${retryCount}次重试: $e"); + DailyLogUtils.writeLog("[蓝牙连接错误] 第${retryCount}次重试: $e"); + + // 延迟重试 + await Future.delayed(const Duration(seconds: 2)); } - var aa; - lisObj = blueteethBindController.currentDevice!.statusStream - .listen((onData) async { + } + + if (connected && !_isDisposed) { + blueteethBindController.blueConnectFlag.value = 2; + + // TopSlideNotification.show( + // context, + // text: "蓝牙连接成功".tr, + // textColor: themeController.currentColor.sc2, + // ); + + lisObj = bledevice.statusStream.listen((onData) async { + if (_isDisposed) return; + if (onData.status == BleEventType.recvLineLog) { final line = onData.val; - print("[bleee]:" + line); - edm.EasyDartModule.logger.info("[bleee]:" + line); - DailyLogUtils.writeLog("[bleee]:" + line); + print("[bleee]: $line"); + edm.EasyDartModule.logger.info("[bleee]: $line"); + DailyLogUtils.writeLog("[bleee]: $line"); } + + // 添加连接状态监听 + if (onData.status == BleEventType.disconnected) { + print("蓝牙连接已断开"); + blueteethBindController.blueConnectFlag.value = 1; + blueteethBindController.updateAll(); + + // 显示断开提示 + // if (!_isDisposed) { + // TopSlideNotification.show( + // context, + // text: "蓝牙连接已断开,请重新连接".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } + _autoReconnect(); + } + if (onData.status == BleEventType.ready) { - aa = await getDeviceNetVersion( - blueteethBindController.currentDevice!, 0); + var aa = await getDeviceNetVersion(bledevice, 0); if (aa == "4g") { - // TopSlideNotification.show( - // Get.context!, - // text: "4g设备配置wifi提示".tr, - // textColor: themeController.currentColor.sc9, - // ); blueteethBindController.netType.value = 2; blueteethBindController.updateAll(); updateDeviceBindStatus( blueteethBindController.currentDeviceMac!.value); - // WidgetsBinding.instance.addPostFrameCallback((_) { - // TopSlideNotification.show( - // context, - // text: "4g设备配置wifi提示".tr, - // textColor: themeController.currentColor.sc9, - // ); - // // Get.back(); - // }); await showTipDialog( context, Text( @@ -1016,64 +1515,57 @@ class _WifiPagePersonState extends State { color: themeController.currentColor.sc3, fontSize: AppConstants().title_text_fontSize), )); - // return; } else if (aa == 'unknown') { blueteethBindController.netType.value = 3; blueteethBindController.updateAll(); - blueteethBindController.updateAll(); - WidgetsBinding.instance.addPostFrameCallback((_) { - TopSlideNotification.show( - context, - text: "获取设备网络类型失败".tr, - textColor: themeController.currentColor.sc9, - ); - }); + TopSlideNotification.show( + context, + text: "获取设备网络类型失败".tr, + textColor: themeController.currentColor.sc9, + ); } else { blueteethBindController.netType.value = 1; blueteethBindController.updateAll(); - // TopSlideNotification.show( - // context, - // text: "当前设备为wifi网络设备".tr, - // textColor: themeController.currentColor.sc9, - // ); blueteethBindController.wifiConnectStatus.value = 0; blueteethBindController.updateAll(); - await initWifiStatusAndWifiList(); + await initWifiStatusAndWifiList(needSuccess: needSuccess); } + dealing = false; } }); } else { - // Navigator.pop(context); TopSlideNotification.show( context, - text: "蓝牙绑定.连接失败".tr, + text: "设备连接失败,请点击刷新重试".tr, textColor: themeController.currentColor.sc9, ); } } catch (e) { - // Navigator.of(Get.context!).pop(); // 关闭加载对话框 + blueteethBindController.blueConnectFlag.value = 1; TopSlideNotification.show( - Get.context!, - text: "设备连接失败".tr, + context, + text: "设备连接失败,请点击刷新重试".tr, textColor: themeController.currentColor.sc9, ); + } finally { + streamlog.close(); } } }); - - // 等待扫描完成 await Future.delayed(Duration(seconds: 20)); } catch (e) { - timeoutTimer?.cancel(); - Navigator.of(Get.context!).pop(); // 关闭加载对话框 - TopSlideNotification.show( - Get.context!, - text: "扫描过程中发生错误".tr, - textColor: themeController.currentColor.sc9, - ); + if (!_isDisposed) { + TopSlideNotification.show( + context, + text: "扫描过程中发生错误".tr, + textColor: themeController.currentColor.sc9, + ); + dealing = false; + } } finally { - timeoutTimer?.cancel(); + _timeoutTimer?.cancel(); await FlutterBluePlus.stopScan(); + dealing = false; } } @@ -1135,4 +1627,94 @@ class _WifiPagePersonState extends State { onFailure: (res) {}, ); } + + Future _cleanupResources() async { + // 取消监听器 + lisObj?.cancel(); + lisObj = null; + + // 取消定时器 + _timeoutTimer?.cancel(); + _timeoutTimer = null; + + // 取消扫描订阅 + _scanSubscription?.cancel(); + _scanSubscription = null; + + await _disconnectDevice(); + // 停止扫描 + FlutterBluePlus.stopScan(); + } + + Future _disconnectDevice() async { + try { + if (blueteethBindController.currentDevice != null) { + await blueteethBindController.currentDevice!.disconnect(); + // blueteethBindController.currentDevice = null; + } + DailyLogUtils.writeLog("关闭蓝牙连接成功".tr); + } catch (e) { + DailyLogUtils.writeError("关闭蓝牙连接失败: $e"); + } finally { + // dealing = false; + } + } + + int _retryCount = 0; + final int _maxRetry = 3; + Future _autoReconnect() async { + if (blueteethBindController.currentDevice == null) return; + + while (_retryCount < _maxRetry) { + try { + print("[蓝牙重连] 第${_retryCount + 1}次尝试连接..."); + await blueteethBindController.currentDevice!.connect(); + + // 检查是否连接成功 + var isConnected = + await blueteethBindController.currentDevice!.isConnected; + if (isConnected) { + print("[蓝牙重连] 连接成功 ✅"); + _retryCount = 0; // 重置重试次数 + return; + } else { + throw Exception("连接失败"); + } + } catch (e) { + _retryCount++; + print("[蓝牙重连] 第$_retryCount次连接失败:$e"); + + if (_retryCount >= _maxRetry) { + print("[蓝牙重连] 已达到最大重试次数($_maxRetry),停止重连 ❌"); + break; + } + + // 等待 2 秒后重试 + await Future.delayed(Duration(seconds: 2)); + } + } + } + + Future restoreWifi(THapp tHapp, aa) async { + if (aa != null && aa is Map) { + return aa; + } + await sendCloseAndOpenWscanCommand(tHapp); + return await getDeviceWifiStatus(blueteethBindController.currentDevice!, 4, + link: true); + } + + Future rebootDeviceCurrent(THapp tHapp, aa) async { + if (aa != null && aa is Map) { + return aa; + } + blueteethBindController.connect_wifi.value = {}; + await rebootDevice( + blueteethBindController.currentDevice!, + ); + await dealWifi(widget.type['mac']); + return await getDeviceWifiStatus(blueteethBindController.currentDevice!, 1, + link: true); + } + } diff --git a/lib/pages/main_bottom/message_page.dart b/lib/pages/main_bottom/message_page.dart index 345d65a..28d3a92 100644 --- a/lib/pages/main_bottom/message_page.dart +++ b/lib/pages/main_bottom/message_page.dart @@ -29,7 +29,7 @@ class _MessagePageState extends State { _pageController = PageController(initialPage: messageController.model.type == 1 ? 0 : 1); messageController.getMessageStatus(); - // _fetchMessageData(); + _fetchMessageData(); } void _fetchMessageData() { diff --git a/lib/pages/mh_page/device/component/DeviceComponentWidget.dart b/lib/pages/mh_page/device/component/DeviceComponentWidget.dart index 90b11cb..ab5971d 100644 --- a/lib/pages/mh_page/device/component/DeviceComponentWidget.dart +++ b/lib/pages/mh_page/device/component/DeviceComponentWidget.dart @@ -20,7 +20,6 @@ import 'package:vbvs_app/model/BleDeviceData.dart'; import 'package:vbvs_app/model/api_response.dart'; import 'package:vbvs_app/pages/mh_page/component/mht_bind_dialog.dart'; import 'package:vbvs_app/pages/mh_page/device/component/tool/BedControlService.dart'; -import 'package:vbvs_app/pages/mh_page/device/component/tool/DeviceType.dart'; import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart'; import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart'; import 'package:vbvs_app/pages/mh_page/homepage/controller/mht_home_controller.dart'; @@ -177,7 +176,7 @@ class _DeviceComponentWidgetState extends State { ), ].divide(SizedBox(width: 33.rpx)), ), - ].divide(SizedBox(height: 37.rpx)), + ].divide(SizedBox(height: 20.rpx)), ), ), CustomCard( @@ -340,7 +339,7 @@ class _DeviceComponentWidgetState extends State { ), ], ), - ].divide(SizedBox(height: 37.rpx)), + ].divide(SizedBox(height: 20.rpx)), ), ); } diff --git a/lib/pages/mh_page/device/component/UpgradeDevice.dart b/lib/pages/mh_page/device/component/UpgradeDevice.dart new file mode 100644 index 0000000..6576c17 --- /dev/null +++ b/lib/pages/mh_page/device/component/UpgradeDevice.dart @@ -0,0 +1,637 @@ +import 'dart:async'; + +import 'package:EasyDartModule/EasyDartModule.dart' as edm; +import 'package:easydevice/easydevice.dart'; +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:mhtctrl/mhtctrl.dart'; +import 'package:vbvs_app/common/color/ServiceConstant.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/color/app_uri_status.dart'; +import 'package:vbvs_app/common/util/BluetoothFirmwareUpdater.dart'; +import 'package:vbvs_app/common/util/DailyLogUtils.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/common/util/requestWithLog.dart'; +import 'package:vbvs_app/component/tool/ClickableContainer.dart'; +import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; +import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; +import 'package:vbvs_app/enum/APPDeviceUpgrade.dart'; +import 'package:vbvs_app/model/BleDeviceData.dart'; +import 'package:vbvs_app/pages/mh_page/device/component/tool/BedControlService.dart'; +import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart'; +import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart'; +import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade.dart'; +import 'package:vbvs_app/pages/mh_page/device/upgrade/tool/device_upgrade_tool.dart'; +import 'package:vbvs_app/pages/mh_page/homepage/controller/mht_home_controller.dart'; + +class UpgradeDevice extends StatefulWidget { + BlueToothDataModel bleDevice; + var deviceType; + + UpgradeDevice({ + super.key, + required this.bleDevice, + required this.deviceType, + }); + + @override + State createState() => _UpgradeDeviceState(); +} + +class _UpgradeDeviceState extends State { + ThemeController themeController = Get.find(); + MHTBlueToothController blueteethBindController = Get.find(); + MHTHomeController homeController = Get.find(); + var lisObj; + late MattressControlService service; + late BedControlService bedService; + MHTBlueToothController mhtBlueToothController = Get.find(); + + @override + Widget build(BuildContext context) { + Map device = { + "name": widget.bleDevice.name, + "mac".tr: widget.bleDevice.mac, + "rssi": widget.bleDevice.rssi, + "bind": widget.bleDevice.bind, + "version": widget.bleDevice.version, + "selected": widget.bleDevice.selected, + }; + // ef.log("[rssi绘制]:--》${widget.bleDevice.rssi}"); + return ClickableContainer( + backgroundColor: Colors.white, + highlightColor: Colors.white, + borderRadius: 20.rpx, + padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 36.rpx, 36.rpx, 36.rpx), + onTap: () async { + widget.bleDevice.selected = !widget.bleDevice.selected!; + device['selected'] = !device['selected']; + mhtBlueToothController.updateAll(); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisSize: MainAxisSize.max, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "${device['name']}", + style: TextStyle( + color: stringToColor("#333333"), fontSize: 30.rpx), + ), + Obx(() { + var aa = mhtBlueToothController.allSelect.value; + print("${aa}"); + double h = 33.rpx; + bool check = device['selected'] ?? false; + if (widget.bleDevice.upgradeStatus! == + APPDeviceUpgrade.upgrading.value) { + return ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.transparent, + padding: EdgeInsets.all(0), + onTap: () { + //取消升级 + MultiDeviceFirmwareUpdater().cancelUpgrade(widget.bleDevice.mac); + }, + child: Row( + children: [ + Text( + "${widget.bleDevice.process}%", + style: TextStyle( + color: themeController.currentColor.sc9, + fontSize: AppConstants().normal_text_fontSize, + ), + ), + Text( + "取消".tr, + style: TextStyle( + color: themeController.currentColor.sc9, + fontSize: AppConstants().normal_text_fontSize, + ), + ), + ], + ), + ); + } + return Container( + height: 33.rpx, + child: Row( + children: [ + AspectRatio( + aspectRatio: 1, + child: Center( + child: Container( + height: h, + width: h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(h / 2), + border: Border.all( + width: check ? 1 : 0.5, + color: Color(0xFFC8CBD2), + ), + ), + child: check + ? Center( + child: ClipOval( + child: Container( + width: h * 0.6, + height: h * 0.6, + color: const Color(0xFF6BFDAC), + ), + ), + ) + : null, + ), + ), + ), + ].divide(SizedBox(width: 10.rpx)), + )); + }), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: MediaQuery.sizeOf(context).width * 0.14, + constraints: BoxConstraints(minWidth: 106.rpx), + child: Text( + "MAC".tr, + style: TextStyle( + color: stringToColor("#929699"), + fontSize: 26.rpx, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Text( + "${device['mac']}", + style: TextStyle( + fontSize: 26.rpx, + color: stringToColor("#333333")), + ), + ].divide(SizedBox(width: 33.rpx)), + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: MediaQuery.sizeOf(context).width * 0.14, + constraints: BoxConstraints(minWidth: 106.rpx), + child: Text( + "信号强度".tr, + style: TextStyle( + color: stringToColor("#929699"), + fontSize: 26.rpx, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Text( + "${widget.bleDevice.rssi}" + "dBm".tr, + style: TextStyle( + fontSize: 26.rpx, + color: stringToColor("#333333")), + ), + ].divide(SizedBox(width: 33.rpx)), + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: MediaQuery.sizeOf(context).width * 0.14, + constraints: BoxConstraints(minWidth: 106.rpx), + child: Text( + "当前版本".tr, + style: TextStyle( + color: stringToColor("#929699"), + fontSize: 26.rpx, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Text( + "${device['version']}", + style: TextStyle( + fontSize: 26.rpx, + color: Colors.black, + ), + ), + ].divide(SizedBox(width: 33.rpx)), + ), + ].divide(SizedBox(height: 20.rpx)), + ), + ), + ], + ), + ].divide(SizedBox(height: 20.rpx)), + ), + ); + } + + //更新设备绑定状态 + void updateDeviceBindStatus(BleDeviceData device) { + String serviceAddress = ServiceConstant.service_address; + String serviceName = ServiceConstant.server_service; + String serviceApi = ServiceConstant.user_setting; + String mac = device.mac!; + String type = "device_bind_status_${mac}"; + String queryUrl = "${serviceAddress}${serviceName}${serviceApi}"; + Map data = { + "type": type, + "mac".tr: mac, + "wifi": false, + "celibration": false, + "person_info": false, + "time": DateTime.now().millisecondsSinceEpoch, + }; + requestWithLog( + logTitle: "更新用户绑定流程".tr, + method: MyHttpMethod.put, + queryUrl: queryUrl, + data: data, + onSuccess: (res) {}, + onFailure: (res) {}, + ); + } + + Widget _buildDeviceInfoSection(device, BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 30.rpx, 0.rpx), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + device.name ?? '蓝牙绑定.默认设备名称'.tr, + style: TextStyle( + fontFamily: 'Inter', + color: const Color(0xFFF6FAFD), + fontSize: 30.rpx, + letterSpacing: 0.0, + ), + ), + Obx(() { + if (blueteethBindController.currentDeviceMac.value == + device.mac) { + return SizedBox( + width: 24.rpx, + height: 24.rpx, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + themeController.currentColor.sc1, + ), + ), + ); + } + return Container(); + }), + ], + ), + ), + Row( + children: [ + Text( + "蓝牙绑定.信号强度".tr + ':${device.rssi ?? '-'}dBm', + style: TextStyle( + fontFamily: 'Inter', + color: const Color(0xFFEBF2F8), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + SizedBox(width: 40.rpx), + Text( + "蓝牙绑定.SN".tr + ':${device.sn ?? '-'}', + style: TextStyle( + fontFamily: 'Inter', + color: const Color(0xFFF5F9FD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ], + ), + Text( + "蓝牙绑定.蓝牙地址".tr + ':${device.deviceId ?? '-'}', + style: TextStyle( + fontFamily: 'Inter', + color: const Color(0xFFF6FAFD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Text( + "蓝牙绑定.mac".tr + ':${device.mac ?? '-'}', + style: TextStyle( + fontFamily: 'Inter', + color: const Color(0xFFF6FAFD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Row( + children: [ + Text( + "蓝牙绑定.网络".tr + + ':${device.isOnline == true ? '蓝牙绑定.在线'.tr : '蓝牙绑定.离线'.tr}', + style: TextStyle( + fontFamily: 'Inter', + color: const Color(0xFFEBF2F8), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + SizedBox(width: 145.rpx), + Row( + children: [ + Text( + "蓝牙绑定.传感器".tr + ":", + style: TextStyle( + fontFamily: 'Inter', + color: const Color(0xFFEBF2F8), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Text( + device.bind == false ? '蓝牙绑定.可绑定'.tr : '蓝牙绑定.已被绑定'.tr, + style: TextStyle( + fontFamily: 'Inter', + color: device.bind == false + ? const Color(0xFF1AD2B5) + : themeController.currentColor.sc9, + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ], + ), + ], + ), + Text( + "版本".tr + '${device.version ?? '-'}', + style: TextStyle( + fontFamily: 'Inter', + color: const Color(0xFFF6FAFD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox( + height: 37.rpx, + )), + ); + } + + //获取智能床/床垫mac + Future getBindTHMAC( + BuildContext context, BlueToothDataModel device, Map deviceType) async { + const int maxRetries = 2; + const Duration timeout = Duration(seconds: 5); + String? macAddress; + try { + // 连接设备 + THapp bledevice = THapp(device: device.scanResult.device); + await bledevice.connect(); + var res2 = bledevice.isConnected; + if (!res2) { + edm.EasyDartModule.logger.error("蓝牙连接失败".tr); + DailyLogUtils.printLog("蓝牙连接失败".tr); + TopSlideNotification.show( + context, + text: "蓝牙连接失败".tr, + textColor: themeController.currentColor.sc9, + ); + throw Exception("蓝牙连接失败".tr); + } + blueteethBindController.blueConnectFlag.value = 2; + blueteethBindController.currentDevice = bledevice; + await Future.delayed(Duration(seconds: 2)); + + if (deviceType['type'] == 3) { + //智能床垫 + macAddress = await getMacFromType3(bledevice, timeout); + } else if (deviceType['type'] == 2) { + //智能床 + macAddress = await getMacFromType2(bledevice, timeout); + } else { + throw Exception("不支持的设备类型".tr); + } + + if (macAddress == null) { + throw Exception("未能获取到MAC地址".tr); + } + + // device.macA = macAddress; + print('MAC地址: $macAddress'); + return macAddress; + } catch (e) { + blueteethBindController.currentDeviceMac.value = ""; + edm.EasyDartModule.logger.error("蓝牙获取MAC失败:$e"); + DailyLogUtils.printLog("蓝牙获取MAC失败:$e"); + TopSlideNotification.show( + context, + // text: e.message ?? "设备连接失败,请重试".tr, + text: "获取不到传感器mac,请重试".tr, + textColor: themeController.currentColor.sc9, + ); + rethrow; + } + } + + fillTHMac( + String mac, BlueToothDataModel bleDevice, BuildContext context) async { + bool flag = false; + String serviceAddress = ServiceConstant.service_address; + String serviceName = ServiceConstant.server_service; + String serviceApi = ServiceConstant.get_bluetooth_device_status; + String queryUrl = "$serviceAddress$serviceName$serviceApi"; + String macParam = + "mac=${Uri.encodeQueryComponent(mac.replaceAll(':', ''))}"; + if (queryUrl.contains('?')) { + queryUrl += '&$macParam'; + } else { + queryUrl += '?$macParam'; + } + + await requestWithLog( + logTitle: "获取设备状态".tr, + method: MyHttpMethod.get, + queryUrl: queryUrl, + onSuccess: (res) { + flag = true; + if (res.code != HttpStatusCodes.ok) return; + + if (res.data != null && res.data is List) { + List responseList = res.data; + + // 查找当前MAC对应的数据 + String macKey = mac.replaceAll(':', '').toUpperCase(); + for (var item in responseList) { + if (item['mac'.tr].toString().toUpperCase() == macKey) { + // 更新 bleDevice 的状态 + //如果传感器已经绑定 暂时不处理 + // bleDevice.bind = item['bind'] ?? bleDevice.bind; + bleDevice.macA = item['mac'.tr]; + if (item['bindMac'] != null && + item['bindMac'].toString().isNotEmpty) { + bleDevice.macB = item['bindMac']; + } + break; + } + } + } + }, + onFailure: (res) { + flag = false; + TopSlideNotification.show(context, + text: "获取设备状态失败".tr, textColor: themeController.currentColor.sc9); + }, + ); + return flag; + } + + Future getMacFromType3(THapp bledevice, Duration timeout) async { + final read = bledevice.getresource('fff0/fff1'); + await read!.characteristic.setNotifyValue(true); + final write = bledevice.getresource('fff0/fff2'); + + const int maxRetries = 2; + for (int attempt = 0; attempt < maxRetries; attempt++) { + final completer = Completer(); + final subscription = read.characteristic.onValueReceived.listen((data) { + if (data.length >= 14) { + completer.complete(parseMacFromBleResponse(data)); + } + }); + + final order = [ + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x00, + 0x03, + 0x40, + 0x01, + 0x01, + 0x00, + 0x45, + 0xFD + ]; + await write!.characteristic.write(order); + + try { + final mac = await completer.future.timeout(timeout); + if (mac == null || mac.isEmpty) { + throw Exception("获取MAC失败".tr); + } + if (mac == "000000000000") { + throw Exception("获取MAC失败".tr); + } + await subscription.cancel(); + return mac; + } catch (_) { + await subscription.cancel(); + if (attempt == maxRetries - 1) rethrow; + } + } + throw Exception("获取MAC超时".tr); + } + + Future getMacFromType2(THapp bledevice, Duration timeout) async { + try { + final read = bledevice.getresource('ffe0/ffe1'); + await read!.characteristic.setNotifyValue(true); + + final write = + bledevice.getresource('ffe0/ffe1'); // 与 read 同 characteristic + const int maxRetries = 2; + for (int attempt = 0; attempt < maxRetries; attempt++) { + final completer = Completer(); + final subscription = read.characteristic.onValueReceived.listen((data) { + if (data.length >= 17) { + completer.complete(parseMacFromTH2Response(data)); + } + }); + + final order = [0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x0C, 0x0B, 0x0A]; + int checksum = order.reduce((a, b) => a + b) & 0xFFFF; + order.add(checksum & 0xFF); // 低位 + order.add((checksum >> 8) & 0xFF); // 高位 + + await write!.characteristic.write(order); + + try { + final mac = await completer.future.timeout(timeout); + await subscription.cancel(); + return mac; + } catch (_) { + await subscription.cancel(); + if (attempt == maxRetries - 1) rethrow; + } + } + } catch (e) { + ef.log("[获取设备 MAC]:失败:$e"); + } + + throw Exception("获取MAC超时".tr); + } +} + +String parseMacFromBleResponse(List data) { + // 先做简单的合法性判断 + if (data.length >= 17 && + data[0] == 0xFF && + data[1] == 0xFF && + data[2] == 0xFF && + data[3] == 0xFF && + data[6] == 0x40 && + data[7] == 0x01) { + // 取出 Byte8 ~ Byte13 + List macBytes = data.sublist(8, 14); + String macAddress = macBytes + .map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase()) + .join(''); + return macAddress; + } else { + throw Exception("BLE返回数据格式不正确".tr); + } +} + +String parseMacFromTH2Response(List data) { + if (data.length < 17) { + throw Exception("数据长度不足,无法解析MAC".tr); + } + + int status = data[8]; + if (status != 0x03 && status != 0x04) { + throw Exception("未连接心率带".tr); + } + + // 提取9~14字节的MAC地址 + List macBytes = data.sublist(9, 15); + return macBytes + .map((b) => b.toRadixString(16).padLeft(2, '0')) + .join(":") + .toUpperCase(); +} diff --git a/lib/pages/mh_page/device/controller/mht_bluetooth_controller.dart b/lib/pages/mh_page/device/controller/mht_bluetooth_controller.dart index c6ddef4..c6becb9 100644 --- a/lib/pages/mh_page/device/controller/mht_bluetooth_controller.dart +++ b/lib/pages/mh_page/device/controller/mht_bluetooth_controller.dart @@ -9,6 +9,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:vbvs_app/common/color/ServiceConstant.dart'; import 'package:vbvs_app/common/color/app_uri_status.dart'; import 'package:vbvs_app/common/util/DailyLogUtils.dart'; +import 'package:vbvs_app/common/util/FirmwareVersionService.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/common/util/requestWithLog.dart'; import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; @@ -24,7 +25,7 @@ class MHTBlueToothModel { double? singal = -100; @JsonKey(ignore: true) - List? blueRawData; //蓝牙原始数据 + List? blueRawData = []; //蓝牙原始数据 @JsonKey(ignore: true) List? deviceDataStatus; //已经请求过状态的数据 @@ -76,7 +77,11 @@ class MHTBlueToothController extends GetControllerEx { RxMap localUpgradeMac = {}.obs; //mac 进度 String? currentUpgradeVersion; //最新版本号 String? currentUpgradeName; //最新固件名 - String? currentUpgradeUrl; //最新固件名 + String? currentUpgradeUrl; //最新固件下载地址 + List firmwareList = []; //固件版本列表 + RxBool allSelect = false.obs; //升级是否全选 + RxBool autoUpgrade = false.obs; //是否自动升级 + void startStatusPolling() { updateDeviceStatus().then((res) { @@ -286,7 +291,7 @@ class MHTBlueToothController extends GetControllerEx { resFlag = true; }, onFailure: (res) { - resFlag = false; + resFlag = false; }, ); return resFlag; diff --git a/lib/pages/mh_page/device/device_maintain.dart b/lib/pages/mh_page/device/device_maintain.dart new file mode 100644 index 0000000..cd141fb --- /dev/null +++ b/lib/pages/mh_page/device/device_maintain.dart @@ -0,0 +1,176 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/color/app_uri_status.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/component/tool/CustomCard.dart'; +import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart'; +import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; +import 'package:vbvs_app/controller/mh_controller/mh_language_controller.dart'; +import 'package:vbvs_app/controller/user_info_controller.dart'; +import 'package:vbvs_app/model/api_response.dart'; + +class DeviceMaintain extends StatefulWidget { + @override + _DeviceMaintainState createState() => _DeviceMaintainState(); +} + +class _DeviceMaintainState extends State { + MHLanguageController languageController = Get.find(); + UserInfoController userInfoController = Get.find(); + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder(builder: (context, bodySize) { + return GestureDetector( + // onTap: () => FocusScope.of(context).unfocus(),, + child: Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/new_background.png'), // 本地图片 + fit: BoxFit.fill, // 填满整个 Container + ), + ), + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + backgroundColor: Colors.transparent, + iconTheme: const IconThemeData(color: Colors.white), + titleSpacing: 0, + automaticallyImplyLeading: false, + title: SizedBox( + width: double.infinity, + height: 180.rpx, + child: Stack( + alignment: Alignment.center, + children: [ + // 中间居中的标题 + Text( + '设备维护'.tr, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 30.rpx, + ), + ), + // 左侧图标 + Positioned( + left: 0.rpx, + child: returnIconButtomNew(), + ), + ], + ), + ), + centerTitle: false, + ), + body: SafeArea( + top: true, + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.rpx, 0.rpx, 0.rpx, 0), + child: CustomCard( + borderRadius: 16.rpx, + onTap: () { + Get.toNamed("/deviceUpgrade"); + }, + colors: [ + Color(0xFF003058), + ], // 渐变色是同一个色,也可以根据需要调整 + child: Container( + width: + // MediaQuery.sizeOf(context).width * 0.66, + bodySize.maxWidth, + height: MediaQuery.sizeOf(context).height * 0.055, + constraints: BoxConstraints( + minWidth: 500.rpx, + minHeight: 90.rpx, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '批量升级'.tr, + style: TextStyle( + color: Colors.white, + fontFamily: 'Inter', + fontSize: + AppConstants().normal_text_fontSize, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox( + width: 17.rpx, + )), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.rpx, 0.rpx, 0.rpx, 0), + child: CustomCard( + borderRadius: 16.rpx, + onTap: () { + // Get.toNamed("/deviceUpgrade"); + NewTopSlideNotification.show( + text: "功能开发中...".tr, + ); + }, + colors: [ + Color(0xFF003058), + ], // 渐变色是同一个色,也可以根据需要调整 + child: Container( + width: + // MediaQuery.sizeOf(context).width * 0.66, + bodySize.maxWidth, + height: MediaQuery.sizeOf(context).height * 0.055, + constraints: BoxConstraints( + minWidth: 500.rpx, + minHeight: 90.rpx, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '远程诊断'.tr, + style: TextStyle( + color: Colors.white, + fontFamily: 'Inter', + fontSize: + AppConstants().normal_text_fontSize, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox( + width: 17.rpx, + )), + ), + ), + ), + ), + ].divide(SizedBox( + height: 20.rpx, + )), + ), + ), + ), + ), + )), + ); + }); + } +} diff --git a/lib/pages/mh_page/device/model/BlueToothDataModel.dart b/lib/pages/mh_page/device/model/BlueToothDataModel.dart index 4a6a58d..d9a9993 100644 --- a/lib/pages/mh_page/device/model/BlueToothDataModel.dart +++ b/lib/pages/mh_page/device/model/BlueToothDataModel.dart @@ -1,4 +1,5 @@ import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:vbvs_app/common/util/FirmwareVersionService.dart'; class BlueToothDataModel { String name; // 设备型号 @@ -13,6 +14,12 @@ class BlueToothDataModel { DateTime lastSeen; // 最后可见时间 String? deviceID; // 设备ID int? version; // ✅ 新增版本号,可为空 + int? rssi; //信号强度 + bool? selected; //是否选中 + int? process = 0; //升级进度 + int? newVersion; //升级版本 + FirmwareVersionInfo? upgradeInfo;//升级固件信息 + int? upgradeStatus;//升级状态 BlueToothDataModel({ this.name = '', @@ -25,6 +32,12 @@ class BlueToothDataModel { required this.lastSeen, this.deviceID, this.version, // ✅ 构造函数参数 + this.rssi, // ✅ 构造函数参数 + this.selected, // ✅ 构造函数参数 + this.process, // ✅ 构造函数参数 + this.newVersion, // ✅ 构造函数参数 + this.upgradeInfo, // ✅ 构造函数参数 + this.upgradeStatus, // ✅ 构造函数参数 }); factory BlueToothDataModel.fromScanResult( @@ -35,6 +48,12 @@ class BlueToothDataModel { String mac = '', String? deviceID, int? version, // ✅ 工厂方法参数 + int? rssi, // ✅ 工厂方法参数 + bool? selected, // ✅ 工厂方法参数 + int? process, // ✅ 工厂方法参数 + int? newVersion, // ✅ 工厂方法参数 + FirmwareVersionInfo? upgradeInfo, // ✅ 工厂方法参数 + int? upgradeStatus, // ✅ 工厂方法参数 }) { String finalName = name.isNotEmpty ? name : (result.advertisementData.localName ?? ''); @@ -50,6 +69,12 @@ class BlueToothDataModel { lastSeen: DateTime.now(), deviceID: deviceID, version: version, // ✅ 赋值 + rssi: rssi, + selected: selected, + process: process, + newVersion: newVersion, + upgradeInfo: upgradeInfo, + upgradeStatus: upgradeStatus, ); } } diff --git a/lib/pages/mh_page/device/upgrade/BatchUpgradeManager.dart b/lib/pages/mh_page/device/upgrade/BatchUpgradeManager.dart new file mode 100644 index 0000000..bca442d --- /dev/null +++ b/lib/pages/mh_page/device/upgrade/BatchUpgradeManager.dart @@ -0,0 +1,165 @@ +// 批量升级封装类 +import 'package:easydevice/easydevice.dart'; +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:vbvs_app/common/util/BluetoothFirmwareUpdater.dart'; +import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart'; +import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart'; +import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade_controller.dart'; + +class BatchUpgradeManager { + static final BatchUpgradeManager _instance = BatchUpgradeManager._internal(); + factory BatchUpgradeManager() => _instance; + BatchUpgradeManager._internal(); + + final MultiDeviceFirmwareUpdater _updater = MultiDeviceFirmwareUpdater(); + final MHTDeviceUpgradeController _upgradeController = Get.find(); + + bool _isBatchUpgrading = false; + final List _batchDevices = []; + + // 开始批量升级 + Future startBatchUpgrade(List devices) async { + if (_isBatchUpgrading) { + throw Exception("批量升级正在进行中".tr); + } + + _isBatchUpgrading = true; + _batchDevices.clear(); + _upgradeController.clearUpgradingList(); + + // 验证设备并添加到升级列表 + for (var device in devices) { + if (device.selected == true) { + _batchDevices.add(device); + _upgradeController.addToUpgradingList(device); + } + } + + if (_batchDevices.isEmpty) { + _isBatchUpgrading = false; + throw Exception("没有选择要升级的设备".tr); + } + + // 开始批量升级 + try { + await _startBatchUpgradeInternal(); + } catch (e) { + _isBatchUpgrading = false; + rethrow; + } + } + + // 内部批量升级实现 + Future _startBatchUpgradeInternal() async { + final List upgradeFutures = []; + + for (var device in _batchDevices) { + final future = _startSingleDeviceUpgrade(device); + upgradeFutures.add(future); + } + + // 等待所有升级完成 + try { + await Future.wait(upgradeFutures, eagerError: false); + } finally { + _isBatchUpgrading = false; + _showBatchUpgradeCompleteNotification(); + } + } + + // 单个设备升级 + Future _startSingleDeviceUpgrade(BlueToothDataModel device) async { + try { + // 创建 THapp 实例 + final thapp = THapp(device: device.scanResult.device); + + // 获取固件URL - 需要您提供实现 + final firmwareUrl = await _getFirmwareUrl(); + if (firmwareUrl == null) { + throw Exception("固件URL为空".tr); + } + + // 使用现有的升级器进行升级 + await _updater.startUpgrade( + thapp: thapp, + mac: device.mac, + firmwareUrl: firmwareUrl, + ); + + // 升级成功,从列表中移除 + _upgradeController.removeFromUpgradingList(device.mac); + + } catch (e) { + // 升级失败,也从列表中移除(或者您可以保留失败设备用于重试) + _upgradeController.removeFromUpgradingList(device.mac); + rethrow; + } + } + + // 获取固件URL - 需要您根据实际情况实现 + Future _getFirmwareUrl() async { + // 这里可以从配置、用户选择或其他地方获取固件URL + // 例如:return _upgradeController.selectedFirmwareUrl; + return null; + } + + // 取消批量升级 + void cancelBatchUpgrade() { + for (var device in _batchDevices) { + if (_updater.isDeviceUpgrading(device.mac)) { + _updater.cancelUpgrade(device.mac); + } + _upgradeController.removeFromUpgradingList(device.mac); + } + + _isBatchUpgrading = false; + _batchDevices.clear(); + + NewTopSlideNotification.show( + text: "批量升级已取消".tr, + textColor: Colors.orange, + ); + } + + // 显示批量升级完成通知 + void _showBatchUpgradeCompleteNotification() { + final allStatus = _updater.getAllUpgradeStatus(); + final completedCount = allStatus.values.where((status) => + status['status'] == 'completed').length; + final failedCount = allStatus.values.where((status) => + status['status'] == 'failed').length; + + String message; + if (failedCount == 0) { + message = "批量升级完成,所有 ${_batchDevices.length} 个设备升级成功".tr; + } else { + message = "批量升级完成,$completedCount 个成功,$failedCount 个失败".tr; + } + + NewTopSlideNotification.show( + text: message, + textColor: failedCount == 0 ? Colors.green : Colors.orange, + ); + } + + // 检查是否正在批量升级 + bool get isBatchUpgrading => _isBatchUpgrading; + + // 获取批量升级统计 + Map getBatchUpgradeStatistics() { + final allStatus = _updater.getAllUpgradeStatus(); + + return { + 'total': _batchDevices.length, + 'completed': allStatus.values.where((status) => + status['status'] == 'completed').length, + 'failed': allStatus.values.where((status) => + status['status'] == 'failed').length, + 'upgrading': allStatus.values.where((status) => + status['status'] == 'upgrading' || status['status'] == 'downloading').length, + 'waiting': allStatus.values.where((status) => + status['status'] == 'waiting').length, + }; + } +} \ No newline at end of file diff --git a/lib/pages/mh_page/device/upgrade/device_upgrade.dart b/lib/pages/mh_page/device/upgrade/device_upgrade.dart new file mode 100644 index 0000000..8efe7b9 --- /dev/null +++ b/lib/pages/mh_page/device/upgrade/device_upgrade.dart @@ -0,0 +1,1165 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/BluetoothHelper.dart'; +import 'package:vbvs_app/common/util/CommonVariables.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/component/tool/ClickableContainer.dart'; +import 'package:vbvs_app/component/tool/CustomCard.dart'; +import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart'; +import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; +import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; +import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; +import 'package:vbvs_app/controller/user_info_controller.dart'; +import 'package:vbvs_app/enum/APPDeviceUpgrade.dart'; +import 'package:vbvs_app/pages/common/selectDialog.dart'; +import 'package:vbvs_app/pages/mh_page/device/component/UpgradeDevice.dart'; +import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart'; +import 'package:vbvs_app/pages/mh_page/device/mht_blueteeth_device_page.dart'; +import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart'; +import 'package:vbvs_app/pages/mh_page/device/upgrade/BatchUpgradeManager.dart'; +import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade_controller.dart'; +import 'package:vbvs_app/pages/mh_page/device/upgrade/tool/device_upgrade_tool.dart'; + +class DeviceUpgrade extends StatefulWidget { + DeviceUpgrade({super.key}); + + @override + State createState() => _DeviceUpgradeState(); +} + +class _DeviceUpgradeState extends State { + MHTBlueToothController mhtBlueToothController = Get.find(); + GlobalController globalController = Get.find(); + UserInfoController userInfoController = Get.find(); + ThemeController themeController = Get.find(); + late FlutterBluePlus flutterBlue; + List scanResults = []; + bool isScanning = false; + Timer? _timer; + bool _isDialogShowing = false; + + var currentConnectedDeviceProp; + var connectDeviceCurrent = null; + List bleDevice = []; + String currentMsg = "寻找设备中..."; + Timer? connectTimer; + bool isFind = false; + StreamSubscription>? _scanSubscription; + var formFieldController = FormFieldController(null); + + MHTDeviceUpgradeController mhtDeviceUpgradeController = Get.find(); + final BatchUpgradeManager _batchManager = BatchUpgradeManager(); + bool _isPopupOpen = false; + + // 新增状态:控制展开/收起 + bool _isExpanded = false; + bool _showFilterPanel = false; + + // 开始批量升级 + Future _startBatchUpgrade() async { + final selectedDevices = mhtDeviceUpgradeController.model.blueRawData! + .where((device) => device.selected == true) + .toList(); + + if (selectedDevices.isEmpty) { + TopSlideNotification.show(context, + text: "请选择要升级的设备".tr, textColor: Colors.red); + return; + } + + try { + await _batchManager.startBatchUpgrade(selectedDevices); + } catch (e) { + TopSlideNotification.show(context, + text: "批量升级启动失败: $e".tr, textColor: Colors.red); + } + } + + @override + void initState() { + super.initState(); + mhtBlueToothController.allSelect.value = false; + flutterBlue = FlutterBluePlus(); + checkBluetoothPermission(); + mhtBlueToothController.search.value = ""; + mhtBlueToothController.currentDeviceMac?.value = ""; + BluetoothHelper.listenBluetoothState((isOn) { + mhtBlueToothController.model.bluetooth = isOn; + mhtBlueToothController.updateAll(); + if (!isOn && !_isDialogShowing) { + _isDialogShowing = true; + mhtDeviceUpgradeController.model.blueRawData = []; + mhtBlueToothController.model.deviceDataStatus = []; + mhtBlueToothController.updateAll(); + showBluetoothNotEnabledDialog(context).then((_) { + _isDialogShowing = false; + }); + } + }); + initFirmwareVersions(formFieldController); + initDeviceUpgradeType(); + } + + //扫描设备 + Future checkBluetoothPermission() async { + PermissionStatus bluetoothStatus = await Permission.bluetooth.status; + PermissionStatus locationStatus = await Permission.location.status; + + if (bluetoothStatus.isGranted && locationStatus.isGranted) { + _startScanning(); + _startPeriodicScan(); + } else { + _requestBluetoothPermission(); + } + } + + Future _requestBluetoothPermission() async { + Map statuses = {}; + bool dialogShown = false; // 标记是否弹过权限提示弹窗 + if (Platform.isIOS) { + PermissionStatus isBleGranted = await Permission.bluetooth.request(); + print('checkBlePermissions-ios, isBleGranted=$isBleGranted'); + if (isBleGranted.isGranted) { + // startBluetoothScanning(); + _startScanning(); + _startPeriodicScan(); + } else { + try { + _startScanning(); + _startPeriodicScan(); + } catch (e) { + TopSlideNotification.show(context, + text: "蓝牙权限未开启,请在设置中开启蓝牙权限".tr, + textColor: themeController.currentColor.sc9); + } + } + } else if (Platform.isAndroid) { + try { + // 检查是否已授权 + bool alreadyGranted = await Permission.bluetoothScan.isGranted && + await Permission.bluetoothConnect.isGranted && + await Permission.location.isGranted; + + if (!alreadyGranted) { + // 弹出自定义提示 + showPermissionInfoDialog( + Get.context!, CommonVariables().bluetoothpermissionInfo); + dialogShown = true; + + await Future.delayed(const Duration(milliseconds: 300)); + + // 请求权限 + statuses = await [ + Permission.bluetoothScan, + Permission.bluetoothConnect, + Permission.location, + ].request(); + + ef.log("权限状态: $statuses"); + } else { + statuses = { + Permission.bluetoothScan: PermissionStatus.granted, + Permission.bluetoothConnect: PermissionStatus.granted, + Permission.location: PermissionStatus.granted, + }; + } + } catch (e) { + ef.log("申请权限出错: $e"); + } finally { + // 只有真的弹过提示才关闭 + if (dialogShown && Get.context != null) { + Navigator.of(Get.context!, rootNavigator: true).pop(); + } + } + bool allGranted = statuses[Permission.bluetoothScan]?.isGranted == true && + statuses[Permission.bluetoothConnect]?.isGranted == true && + statuses[Permission.location]?.isGranted == true; + + if (allGranted) { + _startScanning(); + _startPeriodicScan(); + } else { + _showPermissionDeniedDialog(context); + } + } else { + TopSlideNotification.show(context, + text: "当前系统不支持蓝牙,无法使用此功能".tr, + textColor: themeController.currentColor.sc9); + } + } + + void _showPermissionDeniedDialog(BuildContext context) { + TopSlideNotification.show(context, + text: "应用需要蓝牙和位置权限才能扫描设备。请授予权限。".tr, + textColor: themeController.currentColor.sc9); + } + + void _startScanning() async { + try { + if (!mounted || isScanning || !mhtBlueToothController.shouldScan.value) + return; + + _scanSubscription?.cancel(); + mhtBlueToothController.updateAll(); + + if (!mhtBlueToothController.model.bluetooth!) return; + + if (!isScanning) { + setState(() { + isScanning = true; + }); + + await FlutterBluePlus.startScan(timeout: Duration(seconds: 10)); + + _scanSubscription = FlutterBluePlus.scanResults.listen((results) { + if (!mounted) return; + + final signalThreshold = mhtBlueToothController.model.singal!; + final searchKey = + mhtBlueToothController.search.value.trim().toLowerCase(); + + final filteredResults = results.map((r) { + Map d = { + "updateTime": DateTime.now().millisecondsSinceEpoch, + "name": r.advertisementData.localName?.trim() ?? + r.advertisementData.advName?.trim() ?? + r.device.name ?? + "", + "rssi": r.rssi, + "device": r.device, + "connectable": r.advertisementData.connectable + }; + + // 从 manufacturerData 解析设备唯一 ID + Map> m_d = r.advertisementData.manufacturerData; + String? deviceId; + + m_d.forEach((key, value) { + if (value == null || value.isEmpty) return; + + if (key == 65517) { + List a = [0, 0, ...value]; + advertisDataFormatter(a, d); // 你原来的处理 + if (d['adData']?['deviceId'] != null) + deviceId = d['adData']['deviceId']; + } else if (key == 11125 && value.length == 8) { + deviceId = ab2str(value.sublist(2, 8)).toUpperCase(); + } + }); + + d['id'] = deviceId ?? r.device.remoteId.str; // fallback UUID + return BlueToothDataModel.fromScanResult( + r, + 1, + bind: false, + name: d['name'], + mac: d['id'], + version: d['adData']?['version'], + rssi: d['rssi'], + selected: mhtBlueToothController.allSelect.value, + ); + }).where((d) { + // 信号强度过滤(用 d.scanResult.rssi 或 d.rssi 都可以) + if (d.scanResult.rssi <= signalThreshold) return false; + + // 搜索关键字过滤 + if (searchKey.isNotEmpty) { + String name = d.name.toLowerCase(); + String mac = d.mac.toLowerCase(); + if (!name.contains(searchKey) && !mac.contains(searchKey)) + return false; + } + + // 名称过滤规则,必须包含 deviceType['reg'] 列表中的某个字符串 + List regList = []; + if (regList.isNotEmpty) { + String lowerName = d.name.toLowerCase(); + bool match = + regList.any((reg) => lowerName.contains(reg.toLowerCase())); + if (!match) return false; + } + + // 修正:不要使用外部的 r,改从 d.scanResult 读取广告数据 + final adv = d.scanResult.advertisementData; + final isTarget = (d.rssi ?? d.scanResult.rssi) > signalThreshold && + (adv.localName == "AITH-V2" || adv.localName == "SLAVE") && + adv.manufacturerData.containsKey(0xFFED); + + if (!isTarget) return false; + + return true; + }).toList(); + + final currentDevices = + mhtDeviceUpgradeController.model.blueRawData ?? []; + final newDevices = []; + + // for (var newDevice in filteredResults) { + // final existingIndex = + // currentDevices.indexWhere((d) => d.mac == newDevice.mac); + // if (existingIndex >= 0) { + // currentDevices[existingIndex] = newDevice; + // } else { + // newDevices.add(newDevice); + // } + // } + + for (var newDevice in filteredResults) { + final existingIndex = + currentDevices.indexWhere((d) => d.mac == newDevice.mac); + + if (existingIndex >= 0) { + // 如果设备已存在,更新信号强度rssi + currentDevices[existingIndex].rssi = newDevice.rssi; + } else { + // 如果设备不存在,添加到新设备列表 + newDevice.upgradeStatus = APPDeviceUpgrade.nothing.value; + newDevices.add(newDevice); + } + } + + setState(() { + mhtDeviceUpgradeController.model.blueRawData = [ + ...currentDevices, + ...newDevices + ]; + mhtBlueToothController.updateAll(); + }); + }); + + await Future.delayed(Duration(seconds: 10)); + await FlutterBluePlus.stopScan(); + + if (mounted) { + setState(() { + isScanning = false; + }); + } + } + } catch (e) { + ef.log("$e"); + } finally { + setState(() { + isScanning = false; + }); + } + } + + void _startPeriodicScan() { + _timer = Timer.periodic(Duration(seconds: 3), (timer) { + if (mhtBlueToothController.shouldScan.value && !isScanning) { + removeOldDevices(); // 先清理老旧设备 + _startScanning(); + } + }); + } + + void _stopScanning() { + if (isScanning) { + FlutterBluePlus.stopScan(); + _scanSubscription?.cancel(); + if (mounted) { + setState(() { + isScanning = false; + }); + } + } + } + + void _stopPeriodicScan() { + _timer?.cancel(); + } + + @override + void dispose() { + _stopPeriodicScan(); + _stopScanning(); + _scanSubscription?.cancel(); + connectTimer?.cancel(); + mhtBlueToothController.stopStatusPolling(); + mhtDeviceUpgradeController.model.blueRawData = []; + mhtBlueToothController.model.deviceDataStatus = []; + BluetoothHelper.cancelListener(); + super.dispose(); + } + + bool isTargetDevice(String? name, List keywords) { + if (name == null) return false; + return keywords.any((k) => name.contains(k)); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, boxConstraints) => GestureDetector( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/new_background.png'), + fit: BoxFit.fill, + ), + ), + child: Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + appBar: AppBar( + iconTheme: IconThemeData(color: themeController.currentColor.sc3), + backgroundColor: Colors.transparent, + automaticallyImplyLeading: false, + titleSpacing: 0, + title: Container( + width: double.infinity, + height: 180.rpx, + child: Stack( + alignment: Alignment.center, + children: [ + Text( + '批量升级'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: themeController.currentColor.sc3, + letterSpacing: 0, + fontSize: 30.rpx, + ), + ), + Positioned( + left: 0, + child: returnIconButtom, + ), + Positioned( + right: 0.rpx, + child: ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.transparent, + padding: + EdgeInsets.fromLTRB(30.rpx, 30.rpx, 50.rpx, 30.rpx), + onTap: () { + // 切换筛选条件显示状态 + setState(() { + _showFilterPanel = !_showFilterPanel; + }); + }, + child: Stack( + children: [ + SizedBox( + width: 34.rpx, + height: 24.rpx, + child: SvgPicture.asset( + 'assets/img/icon/upgrade.svg', + fit: BoxFit.cover, + color: Colors.white, + ), + ), + // 使用 Obx 监听升级设备数量 + Obx(() { + final upgradingCount = mhtDeviceUpgradeController + .model.upgradingDevices?.length ?? + 0; + return upgradingCount > 0 + ? Positioned( + right: 0, + top: -2.rpx, + child: Container( + padding: EdgeInsets.all(4.rpx), + decoration: BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + child: Text( + upgradingCount.toString(), + style: TextStyle( + color: Colors.white, + fontSize: 12.rpx, + fontWeight: FontWeight.bold, + ), + ), + ), + ) + : SizedBox.shrink(); + }), + ], + ), + ), + ), + ], + ), + ), + actions: [], + centerTitle: false, + ), + body: Stack( + children: [ + // 主内容区域 + SafeArea( + top: true, + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Column( + children: [ + Container( + width: double.infinity, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.transparent, + Colors.transparent, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + borderRadius: BorderRadius.circular(20.rpx), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 21.rpx, 5.rpx, 21.rpx, 0.rpx), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Text( + '最小信号强度'.tr, + style: TextStyle( + fontFamily: 'Inter', + color: stringToColor("#FFFFFF"), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Expanded( + child: Obx(() { + return Slider( + activeColor: Color(0xFF1FCC9B), + inactiveColor: Colors.white, + thumbColor: Colors.white, + min: -100, + max: 50, + value: mhtBlueToothController + .model.singal!, + onChanged: (newValue) { + newValue = double.parse( + newValue.toStringAsFixed(0)); + mhtBlueToothController + .model.singal = newValue; + mhtDeviceUpgradeController + .model.blueRawData = + mhtDeviceUpgradeController + .model.blueRawData! + ?.where((device) => + device.rssi != null && + device.rssi! >= + mhtBlueToothController + .model + .singal!) + .toList(); + mhtBlueToothController + .updateAll(); + }, + ); + }), + ), + Obx(() { + return Text( + '${mhtBlueToothController.model.singal!.toInt()}', + style: TextStyle( + fontFamily: 'Inter', + color: stringToColor("#FFFFFF"), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ); + }), + ].divide(SizedBox(width: 30.rpx)), + ), + ), + ), + Container( + width: double.infinity, + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 21.rpx, 0.rpx, 0.rpx, 5.rpx), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Theme( + data: Theme.of(context).copyWith( + splashColor: + stringToColor("#011D33"), + highlightColor: + stringToColor("#011D33"), + ), + child: ScrollbarTheme( + data: ScrollbarThemeData( + thumbColor: + MaterialStateProperty.all( + Colors.transparent), + trackColor: + MaterialStateProperty.all( + Colors.transparent), + trackBorderColor: + MaterialStateProperty.all( + Colors.transparent), + ), + child: ValueListenableBuilder( + valueListenable: + formFieldController, + builder: (c, a, s) => + FlutterFlowDropDown( + controller: + formFieldController, + options: + mhtDeviceUpgradeController + .firmwareList! + .map((d) => + "${d.version}") + .toList(), + optionLabels: + mhtDeviceUpgradeController + .firmwareList! + .map((d) => + "${d.version}") + .toList(), + onChanged: (val) { + var selectedVersion = + mhtDeviceUpgradeController + .firmwareList! + .firstWhere( + (d) => d.version == val, + ); + if (selectedVersion != + null) { + mhtDeviceUpgradeController + .selectVerison = + selectedVersion; + final MHTBlueToothController + _btController = + Get.find(); + _btController + .currentUpgradeVersion = + mhtDeviceUpgradeController + .selectVerison! + .version!; + _btController + .currentUpgradeName = + mhtDeviceUpgradeController + .selectVerison! + .fileName!; + _btController + .currentUpgradeUrl = + mhtDeviceUpgradeController + .selectVerison! + .downloadUrl!; + } + }, + width: 300.rpx, + height: 81.rpx, + maxHeight: 300.rpx, + textStyle: TextStyle( + fontSize: 28.rpx, + overflow: + TextOverflow.ellipsis, + color: Colors.black, + ), + hintText: '选择固件版本'.tr, + icon: Icon( + Icons + .keyboard_arrow_down_rounded, + color: Color(0xFF929699), + size: 30.rpx, + ), + fillColor: Colors.white, + elevation: 2, + borderColor: + Colors.transparent, + borderWidth: 2, + borderRadius: AppConstants() + .normal_container_radius, + margin: EdgeInsetsDirectional + .fromSTEB(32.rpx, 8.rpx, + 22.rpx, 8.rpx), + hidesUnderline: true, + isOverButton: false, + isSearchable: false, + isMultiSelect: false, + ), + ), + ), + ), + ClickableContainer( + backgroundColor: + Colors.transparent, + highlightColor: + Colors.transparent, + padding: EdgeInsets.all(0), + onTap: () { + mhtBlueToothController + .autoUpgrade.value = + !mhtBlueToothController + .autoUpgrade.value; + mhtBlueToothController + .updateAll(); + }, + child: Obx(() { + double h = 33.rpx; + bool check = + mhtBlueToothController + .autoUpgrade.value; + return Container( + height: 33.rpx, + child: Row( + children: [ + AspectRatio( + aspectRatio: 1, + child: Center( + child: Container( + height: h, + width: h, + decoration: + BoxDecoration( + borderRadius: + BorderRadius + .circular( + h / 2), + border: + Border.all( + width: check + ? 1 + : 0.5, + color: Color( + 0xFFC8CBD2), + ), + ), + child: check + ? Center( + child: + ClipOval( + child: + Container( + width: + h * 0.6, + height: + h * 0.6, + color: + const Color(0xFF6BFDAC), + ), + ), + ) + : null, + ), + ), + ), + Text( + "自动升级".tr, + style: TextStyle( + fontFamily: 'Inter', + color: + stringToColor( + "#FFFFFF"), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox( + width: 10.rpx)), + )); + })), + ].divide(SizedBox(width: 30.rpx)), + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.transparent, + padding: EdgeInsets.fromLTRB( + 0.rpx, 0.rpx, 20.rpx, 0.rpx), + onTap: () { + //刷新信号强度以及重置设备录入强度排行 + }, + child: SizedBox( + width: 28.rpx, + height: 28.rpx, + child: SvgPicture.asset( + 'assets/img/icon/refresh.svg', + fit: BoxFit.cover, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 0.rpx, 0, 0.rpx), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 19.rpx, 0, 0, 0), + child: Obx(() { + return Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + '匹配出的外围设备'.tr + + "(${getRemainingDeviceCount()})", + style: TextStyle( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc3, + ), + ), + Row( + children: [ + ClickableContainer( + backgroundColor: + Colors.transparent, + highlightColor: + Colors.transparent, + padding: EdgeInsets.all(0), + onTap: () { + mhtBlueToothController + .allSelect.value = + !mhtBlueToothController + .allSelect.value; + + if (mhtBlueToothController + .allSelect.value) { + for (var device + in mhtDeviceUpgradeController + .model + .blueRawData!) { + device.selected = true; + } + } else { + for (var device + in mhtDeviceUpgradeController + .model + .blueRawData!) { + device.selected = false; + } + } + mhtBlueToothController + .updateAll(); + }, + child: Obx(() { + double h = 33.rpx; + bool check = + mhtBlueToothController + .allSelect.value; + return Container( + height: 33.rpx, + child: Row( + children: [ + Text( + mhtBlueToothController + .allSelect + .value == + false + ? "全选".tr + : "取消全选".tr, + style: TextStyle( + fontFamily: + 'Inter', + color: + stringToColor( + "#85F5FF"), + fontSize: 26.rpx, + letterSpacing: + 0.0, + ), + ), + ].divide(SizedBox( + width: 10.rpx)), + )); + }), + ), + ], + ) + ], + ); + }), + ), + ), + ), + //设备列表 + // Obx(() { + // if (mhtDeviceUpgradeController + // .model.blueRawData!.isNotEmpty) { + // final allDevices = mhtDeviceUpgradeController + // .model.blueRawData!; + // final upgradingDevices = + // mhtDeviceUpgradeController + // .model.upgradingDevices ?? + // []; + // final upgradingDevicesMacs = upgradingDevices + // .map((device) => device.mac) + // .toList(); + + // // 当前选择的升级版本 + // final selectedVersion = + // mhtDeviceUpgradeController.selectVerison; + + // // 过滤条件: + // // 1. 不在正在升级列表中 + // // 2. 且版本号不等于当前选择的版本(同版本不需要升级) + // var filteredDevices = + // allDevices.where((device) { + // // bool notUpgrading = !upgradingDevicesMacs + // // .contains(device.mac); + // bool notUpgrading = true; + // //todo wyf 恢复 + // // bool needUpgrade = + // // device.version.toString() != + // // selectedVersion!.version; + // bool needUpgrade = true; + // return notUpgrading && needUpgrade; + // }).toList(); + + // // 若需要过滤设备类型,可继续使用你的方法 + // var deviceList = + // filterDeviceType(filteredDevices); + + // // 按信号强度排序 + // final sortedList = deviceList + // ..sort( + // (a, b) => b.rssi!.compareTo(a.rssi!)); + + // return Expanded( + // child: Container( + // width: double.infinity, + // child: SingleChildScrollView( + // child: Column( + // mainAxisSize: MainAxisSize.max, + // children: [ + // ...sortedList + // .map((device) { + // return UpgradeDevice( + // bleDevice: device, + // deviceType: 1, + // ); + // }) + // .toList() + // .divide( + // SizedBox(height: 30.rpx)) + // .addToEnd( + // SizedBox(height: 30.rpx)), + // ], + // ), + // ), + // ), + // ); + // } + // return Container(); + // }) + + Obx(() { + final allDevices = mhtDeviceUpgradeController + .model.blueRawData ?? + []; + final upgradingDevices = + mhtDeviceUpgradeController + .model.upgradingDevices ?? + []; + + // 合并列表显示 + // List displayDevices = [ + // ...allDevices.map((d) { + // final upgrading = + // upgradingDevices.firstWhereOrNull( + // (u) => u.mac == d.mac); + // return upgrading ?? d; + // }), + // // 如果有升级设备不在 rawData 中,也加上 + // ...upgradingDevices + // .where((u) => !allDevices + // .any((d) => d.mac == u.mac)) + // .toList(), + // ]; + List displayDevices = [ + // 用 upgrading 覆盖 raw 中的相同 mac + ...allDevices.map((d) { + final u = upgradingDevices.firstWhereOrNull( + (x) => x.mac == d.mac); + return u ?? d; + }), + // 加入那些只在 upgradingDevices 中但不在 raw 的 + ...upgradingDevices.where((u) => + !allDevices.any((d) => d.mac == u.mac)), + ]; + + // 可以按信号强度排序 + displayDevices.sort((a, b) => + (b.rssi ?? 0).compareTo(a.rssi ?? 0)); + + return Expanded( + child: Container( + width: double.infinity, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + ...displayDevices + .map((device) { + return UpgradeDevice( + bleDevice: device, + deviceType: 1, + ); + }) + .toList() + .divide(SizedBox(height: 30.rpx)) + .addToEnd( + SizedBox(height: 30.rpx)), + ], + ), + ), + ), + ); + }), + ].divide(SizedBox(height: 30.rpx)), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 0.rpx, 0, 30.rpx), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.rpx), + border: Border.all( + color: themeController.currentColor.sc4 + .withOpacity(0.5), + width: AppConstants().border_width, + ), + ), + child: Padding( + padding: EdgeInsets.only(bottom: 0.rpx), + child: CustomCard( + borderRadius: 16.rpx, + gradientDirection: GradientDirection.vertical, + onTap: () { + String deviceCountFlag = + judgeUpgradeDeviceCount(); + if (deviceCountFlag != null && + deviceCountFlag != "") { + NewTopSlideNotification.show( + text: deviceCountFlag, + textColor: + themeController.currentColor.sc9); + return; + } + mhtDeviceUpgradeController.startUpgrade(); + }, + colors: const [ + Color(0xFFFCFCFC), + Color(0xFFF8FAF9), + Color(0XFFECF6F3), + Color(0XFFD9F0E9), + Color(0xFFCEECE3) + ], + child: Container( + width: double.infinity, + height: 90.rpx, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + ), + child: Text("升级".tr, + style: TextStyle( + color: const Color(0xFF003058), + fontSize: 26.rpx)), + ), + )), + ), + ), + ].divide(SizedBox(height: 30.rpx)), + ), + ), + ), + + // 筛选条件面板 - 从AppBar下方开始显示,遮住主内容 + if (_showFilterPanel) + Positioned.fill( + top: 0.rpx, // 从AppBar下方开始 + child: Container( + color: Colors.black.withOpacity(0.5), // 半透明遮罩 + child: GestureDetector( + onTap: () { + // 点击遮罩区域关闭筛选面板 + setState(() { + _showFilterPanel = false; + }); + }, + child: Container( + color: Colors.transparent, + child: Column( + children: [ + // 筛选条件内容 + Container( + width: double.infinity, + margin: + EdgeInsets.symmetric(horizontal: 30.rpx), + decoration: BoxDecoration( + color: stringToColor("#003058"), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(12.rpx), + bottomRight: Radius.circular(12.rpx), + topLeft: Radius.circular(12.rpx), + topRight: Radius.circular(12.rpx), + ), + boxShadow: [ + BoxShadow( + color: Colors.black26, + blurRadius: 8.rpx, + offset: Offset(0, 4.rpx), + ), + ], + ), + child: getQueryList(), // 使用原来的筛选条件内容 + ), + // 剩余空间可以点击关闭 + Expanded( + child: GestureDetector( + onTap: () { + setState(() { + _showFilterPanel = false; + }); + }, + child: Container( + color: Colors.transparent, + ), + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/mh_page/device/upgrade/device_upgrade_controller.dart b/lib/pages/mh_page/device/upgrade/device_upgrade_controller.dart new file mode 100644 index 0000000..440bd93 --- /dev/null +++ b/lib/pages/mh_page/device/upgrade/device_upgrade_controller.dart @@ -0,0 +1,244 @@ +import 'package:easydevice/easydevice.dart'; +import 'package:ef/ef.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:vbvs_app/common/util/BluetoothFirmwareUpdater.dart'; +import 'package:vbvs_app/common/util/FirmwareVersionService.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/component/tool/NewTopSlideNotification.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/model/BlueToothDataModel.dart'; + +part 'device_upgrade_controller.g.dart'; + +@JsonSerializable() +class MHTDeviceUpgradeModel { + bool? bluetooth = false; // 蓝牙开关 + double? singal = -100; + + @JsonKey(ignore: true) + List? blueRawData = []; //蓝牙原始数据 + @JsonKey(ignore: true) + List? upgradingDevices = []; // 正在升级的设备列表 + + MHTDeviceUpgradeModel(); + + static MHTDeviceUpgradeModel fromJson(Map json) => + _$MHTDeviceUpgradeModelFromJson(json); + Map toJson() => _$MHTDeviceUpgradeModelToJson(this); +} + +class MHTDeviceUpgradeController + extends GetControllerEx { + MHTDeviceUpgradeController() { + attr = GetModel(MHTDeviceUpgradeModel()).obs; + } + + //选择的固件 + FirmwareVersionInfo? selectVerison; //当前选中的固件信息 + int maxLimit = 5; + MHTBlueToothController mhtBlueToothController = Get.find(); + int? upgradeType = 1; + + List? firmwareList = []; + + RxList? selectedDiseaseIds = [].obs; //已选中的列表 + RxList diseaseList = [].obs; //升级类型列表 + + // 添加设备到升级列表 + void addToUpgradingList(BlueToothDataModel device) { + if (!model.upgradingDevices!.any((d) => d.mac == device.mac)) { + model.upgradingDevices!.add(device); + update(); + } + } + + // 从升级列表移除设备 + void removeFromUpgradingList(String mac) { + //todo 移除蓝牙连接 + model.upgradingDevices!.removeWhere((device) => device.mac == mac); + update(); + } + + // 清空升级列表 + void clearUpgradingList() { + //todo 移除蓝牙连接 + model.upgradingDevices!.clear(); + update(); + } + + // 获取正在升级的设备数量 + int get upgradingDeviceCount => model.upgradingDevices?.length ?? 0; + + // 检查设备是否正在升级 + bool isDeviceUpgrading(String mac) { + return model.upgradingDevices!.any((device) => device.mac == mac); + } + + //开始更新 + Future startUpgrade() async { + // 获取全部设备数据 + var allDevices = model.blueRawData; + + // 获取正在升级的设备数据 + var upgradingDevices = model.upgradingDevices; + + // 筛选出所有选中的设备 + var selectedDevices = + allDevices!.where((device) => device.selected == true).toList(); + + // 排除正在升级的设备(即将已经在升级中的设备从选中的设备列表中移除) + var devicesToUpgrade = selectedDevices.where((device) { + // 检查设备是否在正在升级的设备列表中 + return !upgradingDevices! + .any((upgradingDevice) => upgradingDevice.mac == device.mac); + }).toList(); + + if (devicesToUpgrade.isEmpty) { + NewTopSlideNotification.show( + text: "没有需要升级的设备".tr, textColor: themeController.currentColor.sc9); + ef.log("没有需要升级的设备"); + return; + } + + // 检查固件信息 + if (selectVerison == null || selectVerison!.downloadUrl.isEmpty) { + NewTopSlideNotification.show( + text: "请先选择有效的固件版本".tr, textColor: themeController.currentColor.sc9); + throw Exception("请先选择有效的固件版本"); + } + + // mhtBlueToothController.currentUpgradeUrl = selectVerison!.downloadUrl; + // 打印最终需要升级的设备 + ef.log("需要升级的设备:${devicesToUpgrade}"); + + for (var device in devicesToUpgrade) { + // await _startSingleDeviceUpgrade(device); + _startSingleDeviceUpgrade(device); + } + if (devicesToUpgrade.isNotEmpty) { + devicesToUpgrade.forEach((device) { + // 添加设备到正在升级列表 + device.process = 0; + device.upgradeInfo = selectVerison; + addToUpgradingList(device); + // 发送升级指令 + // mhtBlueToothController.sendUpgradeCommand(device); + }); + } + print(model.upgradingDevices!); + } + + // 启动单个设备升级 + Future _startSingleDeviceUpgrade(BlueToothDataModel device) async { + try { + // 更新设备状态 + device.process = 0; + device.upgradeInfo = selectVerison; + device.selected = false; // 升级开始后取消选中状态 + device.upgradeStatus = APPDeviceUpgrade.upgrading.value; + + // 添加到升级列表 + addToUpgradingList(device); + + // 创建 THapp 实例 + final thapp = THapp(device: device.scanResult.device); + + // 确保设备连接 + if (!thapp.isConnected) { + await thapp.device.connect(); + } + + // 使用 MultiDeviceFirmwareUpdater 开始升级 + await MultiDeviceFirmwareUpdater().startUpgrade( + thapp: thapp, + mac: device.mac, + firmwareUrl: selectVerison!.downloadUrl, + ); + + ef.log("设备 ${device.mac} 升级任务已启动"); + } catch (e, stack) { + ef.log("设备 ${device.mac} 升级启动失败: $e\n$stack"); + + // 更新设备状态为失败 + device.process = -1; + device.upgradeStatus = APPDeviceUpgrade.fail.value; + update(); + + // 从升级列表中移除失败设备(可选,取决于业务需求) + // removeFromUpgradingList(device.mac); + + rethrow; + } + } + + // 取消单个设备升级 + void cancelUpgrade(String mac) { + try { + MultiDeviceFirmwareUpdater().cancelUpgrade(mac); + removeFromUpgradingList(mac); + ef.log("设备 $mac 升级已取消"); + + } catch (e, stack) { + ef.log("取消设备 $mac 升级失败: $e\n$stack"); + } + } + + // 取消所有设备升级 + void cancelAllUpgrades() { + try { + MultiDeviceFirmwareUpdater().cancelAllUpgrades(); + clearUpgradingList(); + ef.log("所有设备升级已取消"); + } catch (e, stack) { + ef.log("取消所有设备升级失败: $e\n$stack"); + } + } + + // 获取设备升级状态 + Map? getUpgradeStatus(String mac) { + return MultiDeviceFirmwareUpdater().getUpgradeStatus(mac); + } + + // 获取所有设备升级状态 + Map> getAllUpgradeStatus() { + return MultiDeviceFirmwareUpdater().getAllUpgradeStatus(); + } + + // 检查设备是否正在升级(包括底层状态) + bool isDeviceInUpgrading(String mac) { + return MultiDeviceFirmwareUpdater().isDeviceUpgrading(mac) || + isDeviceUpgrading(mac); + } + + // 清理已完成的任务 + void cleanupCompletedTasks() { + MultiDeviceFirmwareUpdater().cleanupCompletedTasks(); + + // 同时清理本地升级列表中已完成的任务 + model.upgradingDevices!.removeWhere((device) { + final status = getUpgradeStatus(device.mac); + return status == null || + ['completed', 'failed', 'cancelled'].contains(status['status']); + }); + update(); + } + + // 更新设备进度(供 MultiDeviceFirmwareUpdater 回调使用) + void updateDeviceProgress(String mac, int progress, String status) { + final device = model.upgradingDevices!.firstWhere((d) => d.mac == mac); + + if (device != null) { + device.process = progress; + + // 如果升级完成或失败,更新相关状态 + if (status == 'completed' && progress == 100) { + device.newVersion = int.parse(selectVerison?.version ?? '0'); + // 可以在这里添加其他完成后的逻辑 + } else if (status == 'failed') { + device.process = -1; + } + update(); + } + } +} diff --git a/lib/pages/mh_page/device/upgrade/device_upgrade_controller.g.dart b/lib/pages/mh_page/device/upgrade/device_upgrade_controller.g.dart new file mode 100644 index 0000000..38c3ca5 --- /dev/null +++ b/lib/pages/mh_page/device/upgrade/device_upgrade_controller.g.dart @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'device_upgrade_controller.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +MHTDeviceUpgradeModel _$MHTDeviceUpgradeModelFromJson( + Map json) => + MHTDeviceUpgradeModel() + ..bluetooth = json['bluetooth'] as bool? + ..singal = (json['singal'] as num?)?.toDouble(); + +Map _$MHTDeviceUpgradeModelToJson( + MHTDeviceUpgradeModel instance) => + { + 'bluetooth': instance.bluetooth, + 'singal': instance.singal, + }; diff --git a/lib/pages/mh_page/device/upgrade/device_upgrade_list.dart b/lib/pages/mh_page/device/upgrade/device_upgrade_list.dart new file mode 100644 index 0000000..6f7969d --- /dev/null +++ b/lib/pages/mh_page/device/upgrade/device_upgrade_list.dart @@ -0,0 +1,568 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/BluetoothHelper.dart'; +import 'package:vbvs_app/common/util/CommonVariables.dart'; +import 'package:vbvs_app/common/util/FirmwareVersionService.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; +import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; +import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; +import 'package:vbvs_app/controller/user_info_controller.dart'; +import 'package:vbvs_app/pages/common/selectDialog.dart'; +import 'package:vbvs_app/pages/mh_page/component/mht_bind_dialog.dart'; +import 'package:vbvs_app/pages/mh_page/device/component/UpgradeDevice.dart'; +import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart'; +import 'package:vbvs_app/pages/mh_page/device/mht_blueteeth_device_page.dart'; +import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart'; +import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade_controller.dart'; + +class DeviceUpgradeList extends StatefulWidget { + DeviceUpgradeList({super.key}); + + @override + State createState() => _DeviceUpgradeState(); +} + +class _DeviceUpgradeState extends State { + MHTBlueToothController mhtBlueToothController = Get.find(); + GlobalController globalController = Get.find(); + UserInfoController userInfoController = Get.find(); + ThemeController themeController = Get.find(); + late FlutterBluePlus flutterBlue; + List scanResults = []; + bool isScanning = false; + Timer? _timer; + bool _isDialogShowing = false; + + var currentConnectedDeviceProp; + var connectDeviceCurrent = null; + List bleDevice = []; + String currentMsg = "寻找设备中..."; + Timer? connectTimer; + bool isFind = false; + StreamSubscription>? _scanSubscription; + var formFieldController = FormFieldController(null); + MHTDeviceUpgradeController mhtDeviceUpgradeController = Get.find(); + + @override + void initState() { + super.initState(); + mhtBlueToothController.allSelect.value = false; + mhtBlueToothController.model.blueRawData = []; + mhtBlueToothController.model.deviceDataStatus = []; + flutterBlue = FlutterBluePlus(); + _checkBluetoothPermission(); + mhtBlueToothController.search.value = ""; + mhtBlueToothController.currentDeviceMac?.value = ""; + BluetoothHelper.listenBluetoothState((isOn) { + mhtBlueToothController.model.bluetooth = isOn; + mhtBlueToothController.updateAll(); + if (!isOn && !_isDialogShowing) { + _isDialogShowing = true; + mhtBlueToothController.model.blueRawData = []; + mhtBlueToothController.model.deviceDataStatus = []; + mhtBlueToothController.updateAll(); + _showBluetoothNotEnabledDialog().then((_) { + _isDialogShowing = false; + }); + } + }); + _initFirmwareVersions(); + } + + //扫描设备 + Future _checkBluetoothPermission() async { + PermissionStatus bluetoothStatus = await Permission.bluetooth.status; + PermissionStatus locationStatus = await Permission.location.status; + + if (bluetoothStatus.isGranted && locationStatus.isGranted) { + _startScanning(); + _startPeriodicScan(); + } else { + _requestBluetoothPermission(); + } + } + + Future _requestBluetoothPermission() async { + Map statuses = {}; + bool dialogShown = false; // 标记是否弹过权限提示弹窗 + if (Platform.isIOS) { + PermissionStatus isBleGranted = await Permission.bluetooth.request(); + print('checkBlePermissions-ios, isBleGranted=$isBleGranted'); + if (isBleGranted.isGranted) { + // startBluetoothScanning(); + _startScanning(); + _startPeriodicScan(); + } else { + try { + _startScanning(); + _startPeriodicScan(); + } catch (e) { + TopSlideNotification.show(context, + text: "蓝牙权限未开启,请在设置中开启蓝牙权限".tr, + textColor: themeController.currentColor.sc9); + } + } + } else if (Platform.isAndroid) { + try { + // 检查是否已授权 + bool alreadyGranted = await Permission.bluetoothScan.isGranted && + await Permission.bluetoothConnect.isGranted && + await Permission.location.isGranted; + + if (!alreadyGranted) { + // 弹出自定义提示 + showPermissionInfoDialog( + Get.context!, CommonVariables().bluetoothpermissionInfo); + dialogShown = true; + + await Future.delayed(const Duration(milliseconds: 300)); + + // 请求权限 + statuses = await [ + Permission.bluetoothScan, + Permission.bluetoothConnect, + Permission.location, + ].request(); + + ef.log("权限状态: $statuses"); + } else { + statuses = { + Permission.bluetoothScan: PermissionStatus.granted, + Permission.bluetoothConnect: PermissionStatus.granted, + Permission.location: PermissionStatus.granted, + }; + } + } catch (e) { + ef.log("申请权限出错: $e"); + } finally { + // 只有真的弹过提示才关闭 + if (dialogShown && Get.context != null) { + Navigator.of(Get.context!, rootNavigator: true).pop(); + } + } + bool allGranted = statuses[Permission.bluetoothScan]?.isGranted == true && + statuses[Permission.bluetoothConnect]?.isGranted == true && + statuses[Permission.location]?.isGranted == true; + + if (allGranted) { + _startScanning(); + _startPeriodicScan(); + } else { + _showPermissionDeniedDialog(context); + } + } else { + TopSlideNotification.show(context, + text: "当前系统不支持蓝牙,无法使用此功能".tr, + textColor: themeController.currentColor.sc9); + } + } + + void _showPermissionDeniedDialog(BuildContext context) { + TopSlideNotification.show(context, + text: "应用需要蓝牙和位置权限才能扫描设备。请授予权限。".tr, + textColor: themeController.currentColor.sc9); + } + + void _startScanning() async { + try { + if (!mounted || isScanning || !mhtBlueToothController.shouldScan.value) + return; + + _scanSubscription?.cancel(); + mhtBlueToothController.updateAll(); + + if (!mhtBlueToothController.model.bluetooth!) return; + + if (!isScanning) { + setState(() { + isScanning = true; + }); + + await FlutterBluePlus.startScan(timeout: Duration(seconds: 10)); + + _scanSubscription = FlutterBluePlus.scanResults.listen((results) { + if (!mounted) return; + + final signalThreshold = mhtBlueToothController.model.singal!; + final searchKey = + mhtBlueToothController.search.value.trim().toLowerCase(); + + final filteredResults = results.map((r) { + Map d = { + "updateTime": DateTime.now().millisecondsSinceEpoch, + "name": r.advertisementData.localName?.trim() ?? + r.advertisementData.advName?.trim() ?? + r.device.name ?? + "", + "rssi": r.rssi, + "device": r.device, + "connectable": r.advertisementData.connectable + }; + + // 从 manufacturerData 解析设备唯一 ID + Map> m_d = r.advertisementData.manufacturerData; + String? deviceId; + + m_d.forEach((key, value) { + if (value == null || value.isEmpty) return; + + if (key == 65517) { + List a = [0, 0, ...value]; + advertisDataFormatter(a, d); // 你原来的处理 + if (d['adData']?['deviceId'] != null) + deviceId = d['adData']['deviceId']; + } else if (key == 11125 && value.length == 8) { + deviceId = ab2str(value.sublist(2, 8)).toUpperCase(); + } + }); + + d['id'] = deviceId ?? r.device.remoteId.str; // fallback UUID + return BlueToothDataModel.fromScanResult( + r, + 1, + bind: false, + name: d['name'], + mac: d['id'], + version: d['adData']?['version'], + rssi: d['rssi'], + selected: mhtBlueToothController.allSelect.value, + ); + }).where((d) { + // 信号强度过滤(用 d.scanResult.rssi 或 d.rssi 都可以) + if (d.scanResult.rssi <= signalThreshold) return false; + + // 搜索关键字过滤 + if (searchKey.isNotEmpty) { + String name = d.name.toLowerCase(); + String mac = d.mac.toLowerCase(); + if (!name.contains(searchKey) && !mac.contains(searchKey)) + return false; + } + + // 名称过滤规则,必须包含 deviceType['reg'] 列表中的某个字符串 + List regList = []; + if (regList.isNotEmpty) { + String lowerName = d.name.toLowerCase(); + bool match = + regList.any((reg) => lowerName.contains(reg.toLowerCase())); + if (!match) return false; + } + + // 修正:不要使用外部的 r,改从 d.scanResult 读取广告数据 + final adv = d.scanResult.advertisementData; + final isTarget = (d.rssi ?? d.scanResult.rssi) > signalThreshold && + (adv.localName == "AITH-V2" || adv.localName == "SLAVE") && + adv.manufacturerData.containsKey(0xFFED); + + if (!isTarget) return false; + + return true; + }).toList(); + + final currentDevices = mhtBlueToothController.model.blueRawData ?? []; + final newDevices = []; + + // for (var newDevice in filteredResults) { + // final existingIndex = + // currentDevices.indexWhere((d) => d.mac == newDevice.mac); + // if (existingIndex >= 0) { + // currentDevices[existingIndex] = newDevice; + // } else { + // newDevices.add(newDevice); + // } + // } + + for (var newDevice in filteredResults) { + final existingIndex = + currentDevices.indexWhere((d) => d.mac == newDevice.mac); + + if (existingIndex >= 0) { + // 如果设备已存在,更新信号强度rssi + currentDevices[existingIndex].rssi = newDevice.rssi; + } else { + // 如果设备不存在,添加到新设备列表 + newDevices.add(newDevice); + } + } + + setState(() { + mhtBlueToothController.model.blueRawData = [ + ...currentDevices, + ...newDevices + ]; + mhtBlueToothController.updateAll(); + }); + }); + + await Future.delayed(Duration(seconds: 10)); + await FlutterBluePlus.stopScan(); + + if (mounted) { + setState(() { + isScanning = false; + }); + } + } + } catch (e) { + ef.log("$e"); + } finally { + setState(() { + isScanning = false; + }); + } + } + + void _startPeriodicScan() { + _timer = Timer.periodic(Duration(seconds: 3), (timer) { + if (mhtBlueToothController.shouldScan.value && !isScanning) { + _removeOldDevices(); // 先清理老旧设备 + _startScanning(); + } + }); + } + + void _stopScanning() { + if (isScanning) { + FlutterBluePlus.stopScan(); + _scanSubscription?.cancel(); + if (mounted) { + setState(() { + isScanning = false; + }); + } + } + } + + void _stopPeriodicScan() { + _timer?.cancel(); + } + + @override + void dispose() { + _stopPeriodicScan(); + _stopScanning(); + _scanSubscription?.cancel(); + connectTimer?.cancel(); + mhtBlueToothController.stopStatusPolling(); + mhtBlueToothController.model.blueRawData = []; + mhtBlueToothController.model.deviceDataStatus = []; + BluetoothHelper.cancelListener(); + super.dispose(); + } + + bool isTargetDevice(String? name, List keywords) { + if (name == null) return false; + return keywords.any((k) => name.contains(k)); + } + + _showBluetoothNotEnabledDialog() async { + await showTipDialog( + backgroundColor: Colors.white, + context, + Column( + children: [ + Text( + "蓝牙未开启".tr, + style: TextStyle( + fontSize: AppConstants().title_text_fontSize, + color: stringToColor("#333333"), + ), + ), + SizedBox( + height: 20.rpx, + ), + Text( + "请先打开蓝牙在进行设备扫描".tr, + style: TextStyle( + fontSize: AppConstants().normal_text_fontSize, + color: stringToColor("#333333"), + ), + ), + ], + )); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, boxConstraints) => GestureDetector( + // onTap: () => FocusScope.of(context).unfocus(),, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/new_background.png'), + fit: BoxFit.fill, + ), + ), + child: Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + appBar: AppBar( + iconTheme: IconThemeData(color: themeController.currentColor.sc3), + backgroundColor: Colors.transparent, + automaticallyImplyLeading: false, + titleSpacing: 0, + title: Container( + width: double.infinity, + height: 180.rpx, + child: Stack( + alignment: Alignment.center, + children: [ + Text( + '升级列表'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: themeController.currentColor.sc3, + letterSpacing: 0, + fontSize: 30.rpx, + ), + ), + Positioned( + left: 0, + child: returnIconButtom, + ), + ], + ), + ), + actions: [], + centerTitle: false, + ), + body: SafeArea( + top: true, + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Column( + children: [ + Obx(() { + if (mhtDeviceUpgradeController.model + .upgradingDevices!.isNotEmpty) { + final sortedList = mhtDeviceUpgradeController.model + .upgradingDevices! + .toList() + ..sort((a, b) => b.rssi!.compareTo(a.rssi!)); + // if (sortedList.length > 0) { + // ef.log("[rssi变化]--》${sortedList[0].rssi}"); + // } + return Expanded( + child: Container( + width: double.infinity, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + ...sortedList + .map((device) { + return UpgradeDevice( + bleDevice: device, + deviceType: 1, + ); + }) + .toList() + .divide(SizedBox(height: 30.rpx)) + .addToEnd(SizedBox(height: 30.rpx)), + ], + ), + ), + ), + ); + } + return Container(); + }), + ].divide(SizedBox( + height: 30.rpx, + )), + )), + ].divide(SizedBox(height: 30.rpx)), + ), + ), + ), + ), + ), + ), + ); + } + + void _removeOldDevices() { + final now = DateTime.now(); + final currentDevices = mhtBlueToothController.model.blueRawData ?? []; + + // 移除30秒内未出现的设备 + final updatedDevices = currentDevices.where((device) { + return now.difference(device.lastSeen) < Duration(seconds: 30); + }).toList(); + + if (updatedDevices.length != currentDevices.length) { + setState(() { + mhtBlueToothController.model.blueRawData = updatedDevices; + }); + } + } + + String getDeviceId(ScanResult result) { + // Android:remoteId 就是 MAC + if (Platform.isAndroid) { + return result.device.remoteId.str.replaceAll(':', '').toUpperCase(); + } + + // iOS:尝试从 manufacturerData 里解析 + Map> mData = result.advertisementData.manufacturerData; + for (var key in mData.keys) { + List? bytes = mData[key]; + if (bytes != null && bytes.length == 6) { + // 假设这 6 个字节就是 MAC + return bytes + .map((b) => b.toRadixString(16).padLeft(2, '0')) + .join('') + .toUpperCase(); + } + } + return result.device.remoteId.str.toUpperCase(); + } + + Future _initFirmwareVersions() async { + try { + const baseUrl = "https://share.file.he-info.cn"; + const firmwarePath = "/ota/aithv2/"; + + final versions = await FirmwareVersionService.getAllFirmwareVersions( + baseUrl: baseUrl, + firmwarePath: firmwarePath, + ); + + if (versions.isNotEmpty) { + // 这里你可以存到控制器里 + mhtBlueToothController.firmwareList = versions; + print("✅ 获取到 ${versions.length} 个固件版本:"); + for (final v in versions) { + print("➡ ${v.fileName} (${v.version})"); + } + if (mhtDeviceUpgradeController.selectVerison == null) { + mhtDeviceUpgradeController.selectVerison = versions.first; + formFieldController.value = + mhtDeviceUpgradeController.selectVerison!.version!; + mhtBlueToothController.updateAll(); + } else { + formFieldController.value = + mhtDeviceUpgradeController.selectVerison!.version!; + } + } else { + print("⚠ 没有获取到固件版本文件"); + } + } catch (e) { + print("❌ 初始化获取固件版本失败: $e"); + } + } +} diff --git a/lib/pages/mh_page/device/upgrade/tool/device_upgrade_tool.dart b/lib/pages/mh_page/device/upgrade/tool/device_upgrade_tool.dart new file mode 100644 index 0000000..cc8424d --- /dev/null +++ b/lib/pages/mh_page/device/upgrade/tool/device_upgrade_tool.dart @@ -0,0 +1,328 @@ +import 'dart:io'; + +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/FirmwareVersionService.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/component/tool/SelectableTagButton.dart'; +import 'package:vbvs_app/enum/APPDeviceUpgrade.dart'; +import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart'; +import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart'; +import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart'; +import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade_controller.dart'; + +MHTDeviceUpgradeController mhtDeviceUpgradeController = Get.find(); + +Future initDeviceUpgradeType() async { + try { + // 模拟异步加载 + // await Future.delayed(const Duration(milliseconds: 300)); + + final dataList = APPDeviceUpgradeExtension.list; + + ef.log("✅ 初始化固件升级类型成功,共 ${dataList.length} 项"); + for (var item in dataList) { + ef.log(" ${item['id']} - ${item['name']}"); + } + // 你可以在这里存入 controller 变量中,例如: + mhtDeviceUpgradeController.diseaseList.value = dataList; + } catch (e) { + ef.log("❌ 初始化获取固件版本失败: $e"); + } +} + +Future initFirmwareVersions(formFieldController) async { + try { + const baseUrl = "https://share.file.he-info.cn"; + const firmwarePath = "/ota/aithv2/"; + + final versions = await FirmwareVersionService.getAllFirmwareVersions( + baseUrl: baseUrl, + firmwarePath: firmwarePath, + ); + + if (versions.isNotEmpty) { + // 这里你可以存到控制器里 + mhtDeviceUpgradeController.firmwareList = versions; + print("✅ 获取到 ${versions.length} 个固件版本:"); + for (final v in versions) { + print("➡ ${v.fileName} (${v.version})"); + } + if (mhtDeviceUpgradeController.selectVerison == null) { + mhtDeviceUpgradeController.selectVerison = versions.first; + formFieldController.value = + mhtDeviceUpgradeController.selectVerison!.version!; + + final MHTBlueToothController _btController = Get.find(); + _btController.currentUpgradeVersion = + mhtDeviceUpgradeController.selectVerison!.version!; + _btController.currentUpgradeName = + mhtDeviceUpgradeController.selectVerison!.fileName!; + _btController.currentUpgradeUrl = + mhtDeviceUpgradeController.selectVerison!.downloadUrl!; + + mhtDeviceUpgradeController.updateAll(); + } else { + formFieldController.value = + mhtDeviceUpgradeController.selectVerison!.version!; + } + mhtDeviceUpgradeController.updateAll(); + } else { + print("⚠ 没有获取到固件版本文件"); + } + } catch (e) { + print("❌ 初始化获取固件版本失败: $e"); + } +} + +getQueryList() { + return Padding( + padding: EdgeInsets.all(AppConstants().content_left_right_padding), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "筛选条件".tr, + style: TextStyle( + fontSize: AppConstants().title_text_fontSize, + color: Colors.white, + ), + ), + Obx(() { + final selectedIds = mhtDeviceUpgradeController.selectedDiseaseIds; + final diseases = mhtDeviceUpgradeController.diseaseList; + return Padding( + padding: EdgeInsetsDirectional.fromSTEB(0.rpx, 40.rpx, 0.rpx, 0), + child: Wrap( + spacing: 20.rpx, + runSpacing: 20.rpx, + children: diseases.map((disease) { + final id = disease['id']; + final name = disease['name']; + final isSelected = selectedIds!.contains(id); + return SelectableTagButton( + selectedTextColor: Colors.black, + unselectedTextColor: Colors.white, + colors: [stringToColor("#84F5FF")], + borderRadius: AppConstants().button_container_radius, + label: name, + selected: isSelected, + onTap: () { + final allId = APPDeviceUpgrade.ALL.value; // 全部的id(一般是1) + + if (id == allId) { + // 👉 点击的是“全部” + if (isSelected) { + // 当前是选中状态 → 取消“全部”选择(清空所有) + selectedIds.clear(); + } else { + // 当前未选中 → 全部选中(添加所有状态的id) + selectedIds + ..clear() + ..addAll(APPDeviceUpgradeExtension.valueList); + } + } else { + // 👉 点击的不是“全部” + if (isSelected) { + // 已选中 → 取消当前id + selectedIds.remove(id); + + // 若取消后,“全部”还在选中 → 去掉“全部” + if (selectedIds.contains(allId)) { + selectedIds.remove(allId); + } + } else { + // 未选中 → 选中当前id + selectedIds.add(id); + + // 若选中的数量 == 除“全部”外的所有类型数 → 自动勾选“全部” + final allOtherIds = APPDeviceUpgradeExtension + .valueList + .where((v) => v != allId); + if (selectedIds.toSet().containsAll(allOtherIds)) { + selectedIds + ..clear() + ..addAll(APPDeviceUpgradeExtension.valueList); + } + } + } + + mhtDeviceUpgradeController.updateAll(); + }); + }).toList(), + ), + ); + }), + ], + ), + ), + ); +} + +// int getRemainingDeviceCount() { +// // 获取所有设备数据 +// var allDevices = mhtDeviceUpgradeController.model.blueRawData!; + +// // 获取正在升级的设备数据 +// var upgradingDevices = mhtDeviceUpgradeController.model.upgradingDevices; + +// // 获取正在升级的设备 mac 地址列表 +// var upgradingMacs = upgradingDevices!.map((device) => device.mac).toList(); + +// // 计算所有设备中不在升级设备列表中的设备数量 +// var remainingDevices = allDevices +// .where((device) => !upgradingMacs.contains(device.mac)) +// .toList(); + +// // 返回剩余设备的数量,最小为 0 + +// mhtDeviceUpgradeController.selectVerison; +// return remainingDevices.length; +// } +int getRemainingDeviceCount() { + // 获取所有设备数据 + var allDevices = mhtDeviceUpgradeController.model.blueRawData ?? []; + + // 获取正在升级的设备数据 + var upgradingDevices = + mhtDeviceUpgradeController.model.upgradingDevices ?? []; + + // 获取当前选择的升级版本 + var selectedVersion = mhtDeviceUpgradeController.selectVerison; + + // 获取正在升级的设备 mac 地址列表 + var upgradingMacs = upgradingDevices.map((device) => device.mac).toList(); + + // 过滤出: + // 1. 不在正在升级的设备列表中; + // 2. 且版本号不等于当前选中的版本(表示无需升级的排除掉) + var remainingDevices = allDevices.where((device) { + // bool notUpgrading = !upgradingMacs.contains(device.mac); + bool notUpgrading = true; + //todo wyf 恢复 + // bool needUpgrade = device.version.toString() != selectedVersion!.version; + bool needUpgrade = true; + return notUpgrading && needUpgrade; + }).toList(); + + // 返回剩余需要升级的设备数量 + + List displayDevices = [ + // 用 upgrading 覆盖 raw 中的相同 mac + ...allDevices.map((d) { + final u = upgradingDevices.firstWhereOrNull((x) => x.mac == d.mac); + return u ?? d; + }), + // 加入那些只在 upgradingDevices 中但不在 raw 的 + ...upgradingDevices.where((u) => !allDevices.any((d) => d.mac == u.mac)), + ]; + return displayDevices.length; + return remainingDevices.length; +} + +String judgeUpgradeDeviceCount() { + List selectedDevices = mhtDeviceUpgradeController.model.blueRawData! + .where((device) => device.selected == true) + .toList(); + if (selectedDevices.length == 0) { + return "请至少选择一项设备"; + } + if (selectedDevices.length > mhtDeviceUpgradeController.maxLimit) { + return "超出数量限制,请等待"; + } + final devices = mhtDeviceUpgradeController.model.upgradingDevices; + if (devices == null || devices.isEmpty) { + return ""; + } else { + if (devices.length >= mhtDeviceUpgradeController.maxLimit) { + return "超出数量限制,请等待"; + } + if (devices.length + selectedDevices.length > + mhtDeviceUpgradeController.maxLimit) { + return "超出数量限制,请等待"; + } + } + return ""; +} + +String getDeviceId(ScanResult result) { + // Android:remoteId 就是 MAC + if (Platform.isAndroid) { + return result.device.remoteId.str.replaceAll(':', '').toUpperCase(); + } + + // iOS:尝试从 manufacturerData 里解析 + Map> mData = result.advertisementData.manufacturerData; + for (var key in mData.keys) { + List? bytes = mData[key]; + if (bytes != null && bytes.length == 6) { + // 假设这 6 个字节就是 MAC + return bytes + .map((b) => b.toRadixString(16).padLeft(2, '0')) + .join('') + .toUpperCase(); + } + } + return result.device.remoteId.str.toUpperCase(); +} + +void removeOldDevices() { + final now = DateTime.now(); + final currentDevices = mhtDeviceUpgradeController.model.blueRawData! ?? []; + + // 移除30秒内未出现的设备 + final updatedDevices = currentDevices.where((device) { + return now.difference(device.lastSeen) < Duration(seconds: 30); + }).toList(); + + if (updatedDevices.length != currentDevices.length) { + mhtDeviceUpgradeController.model.blueRawData = updatedDevices; + mhtDeviceUpgradeController.updateAll(); + } +} + +showBluetoothNotEnabledDialog(BuildContext context) async { + await showTipDialog( + backgroundColor: Colors.white, + context, + Column( + children: [ + Text( + "蓝牙未开启".tr, + style: TextStyle( + fontSize: AppConstants().title_text_fontSize, + color: stringToColor("#333333"), + ), + ), + SizedBox( + height: 20.rpx, + ), + Text( + "请先打开蓝牙在进行设备扫描".tr, + style: TextStyle( + fontSize: AppConstants().normal_text_fontSize, + color: stringToColor("#333333"), + ), + ), + ], + )); +} + +List filterDeviceType(List devices) { + final selectedIds = mhtDeviceUpgradeController.selectedDiseaseIds; + + // 如果未选择或包含“全部”,直接返回全部设备 + if (selectedIds!.isEmpty || + selectedIds.contains(APPDeviceUpgrade.ALL.value)) { + return devices; + } + + // 否则按类型过滤 + return devices + .where((device) => selectedIds.contains(device.upgradeStatus)) + .toList(); +} diff --git a/lib/pages/mh_page/homepage/new_Home_page.dart b/lib/pages/mh_page/homepage/new_Home_page.dart index 9334bde..a60290f 100644 --- a/lib/pages/mh_page/homepage/new_Home_page.dart +++ b/lib/pages/mh_page/homepage/new_Home_page.dart @@ -236,19 +236,23 @@ class _NewHomePageState extends State { ); }), const Spacer(), // 左右分隔 - FloatingSvgIcon( - assetPath: 'assets/img/icon/xiaoyi.svg', - width: 60.rpx, - height: 60.rpx, - onTap: () { - // print("点击了小鹅图标"); - if (userInfoController.model.login == 0) { - Get.toNamed("/loginPage"); - } - Get.toNamed("/xiaoEPage", - arguments: "https://xiaoe.he-info.cn/"); - }, - ), + if (userInfoController.model.login != null && + userInfoController.model.login != 0 && + userInfoController.model.user!.phone != null && + userInfoController.model.user!.phone != "17649984946") + FloatingSvgIcon( + assetPath: 'assets/img/icon/xiaoyi.svg', + width: 60.rpx, + height: 60.rpx, + onTap: () { + // print("点击了小鹅图标"); + if (userInfoController.model.login == 0) { + Get.toNamed("/loginPage"); + } + Get.toNamed("/xiaoEPage", + arguments: "https://xiaoe.he-info.cn/"); + }, + ), SizedBox(width: 40.rpx), ], ), @@ -563,6 +567,7 @@ class _NewHomePageState extends State { ), ), ), + InkWell( onTap: () { if (formFieldController diff --git a/lib/pages/mh_page/new_settingPage.dart b/lib/pages/mh_page/new_settingPage.dart index b3cca13..0433090 100644 --- a/lib/pages/mh_page/new_settingPage.dart +++ b/lib/pages/mh_page/new_settingPage.dart @@ -490,6 +490,65 @@ class _SettingPageState extends State { ), ), ), + if (userInfoController.model.user != null && + userInfoController.model.user!.fac != + null && + userInfoController.model.user!.fac == + true) + ClickableContainer( + backgroundColor: + Colors.transparent, // 容器背景色 + highlightColor: themeController + .currentColor.sc21, // 点击时的背景色 + padding: EdgeInsetsDirectional.fromSTEB( + 0.rpx, 0.rpx, 0.rpx, 0.rpx), + onTap: () { + // Get.toNamed("/privacyPolicyPage"); + Get.toNamed("/deviceMaintain"); + }, + child: Container( + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 40.rpx, + 30.rpx, + 40.rpx, + 30.rpx), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Text( + '设备维护'.tr, + style: TextStyle( + fontFamily: 'Inter', + color: Colors.white, + fontSize: AppConstants() + .title_text_fontSize, + letterSpacing: 0.0, + // height: 1.0, + ), + ), + ].divide( + SizedBox(width: 22.rpx)), + ), + SvgPicture.asset( + 'assets/img/icon/arrow_right.svg', + width: 8.rpx, + height: 14 + .rpx, // 如果 SVG 中没有固定颜色,可以这样设置 + color: themeController + .currentColor.sc3, + ), + ], + ), + ), + ), + ), ] .divide(SizedBox(height: 0.rpx)) .addToStart(SizedBox(height: 30.rpx)) diff --git a/lib/routers/mh_routers.dart b/lib/routers/mh_routers.dart index bc5ae58..fd5ccbb 100644 --- a/lib/routers/mh_routers.dart +++ b/lib/routers/mh_routers.dart @@ -19,6 +19,9 @@ import 'package:vbvs_app/pages/mh_page/apply_repair_page.dart'; import 'package:vbvs_app/pages/mh_page/book_info_page.dart'; import 'package:vbvs_app/pages/mh_page/book_success_page.dart'; import 'package:vbvs_app/pages/mh_page/delete_account.dart'; +import 'package:vbvs_app/pages/mh_page/device/device_maintain.dart'; +import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade.dart'; +import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade_list.dart'; import 'package:vbvs_app/pages/mh_page/device/mht_bind_device_success.dart'; import 'package:vbvs_app/pages/mh_page/device/mht_bind_device_type.dart'; import 'package:vbvs_app/pages/mh_page/device/mht_blueteeth_device_page.dart'; @@ -140,6 +143,9 @@ var mhroutes = { "/privacyPolicyPageNew": (contxt, {arguments}) => PrivacyPolicyNewPage(sleepUri: arguments), "/commonMessageSettingPage": (contxt) => MHTCommonMessageSettingPage(), + "/deviceMaintain": (contxt) => DeviceMaintain(), + "/deviceUpgrade": (contxt) => DeviceUpgrade(), + "/deviceUpgradeList": (contxt) => DeviceUpgradeList(), }; var mhonGenerateRoute = (RouteSettings settings) { final String? name = settings.name; // 获取路由名称,如 /news 或 /search diff --git a/pubspec.yaml b/pubspec.yaml index 7371303..bd44612 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,7 +8,7 @@ environment: sdk: ^3.5.4 fluwx: - app_id: 'wx929c548fea6af9c7' #填写自己的 WeChat app id.眠花糖 + # app_id: 'wx929c548fea6af9c7' #填写自己的 WeChat app id.眠花糖 # app_id: 'wxeb2688220799e2c5' #填写自己的 app id.太和e护 debug_logging: false # Logging in debug mode. android: