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/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.spaceBetween, 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)), ), Obx(() { var aa = mhtBlueToothController.allSelect.value; return Text( "${APPDeviceUpgradeExtension.nameFromInt(widget.bleDevice.upgradeStatus)}", style: TextStyle( color: stringToColor("#929699"), fontSize: 26.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(); }