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/app_uri_status.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/CustomCard.dart'; import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; 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'; import 'package:vbvs_app/pages/mh_page/test/WebviewTestModel.dart'; class DeviceComponentWidget extends StatefulWidget { BlueToothDataModel bleDevice; var deviceType; DeviceComponentWidget({ super.key, required this.bleDevice, required this.deviceType, }); @override State createState() => _DeviceComponentWidgetState(); } class _DeviceComponentWidgetState extends State { ThemeController themeController = Get.find(); MHTBlueToothController blueteethBindController = Get.find(); MHTHomeController homeController = Get.find(); var lisObj; late MattressControlService service; late BedControlService bedService; @override Widget build(BuildContext context) { Map device = { "name": widget.bleDevice.name, "mac".tr: widget.bleDevice.mac, "rssi": widget.bleDevice.scanResult.rssi, "bind": widget.bleDevice.bind, }; return ClickableContainer( backgroundColor: Colors.white, highlightColor: Colors.white, borderRadius: 20.rpx, padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 36.rpx, 55.rpx, 36.rpx), onTap: () async {}, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ Row( children: [ Text( "${device['name']}", style: TextStyle( color: stringToColor("#333333"), fontSize: 30.rpx), ), SizedBox( width: 10.rpx, ), Obx(() { if (blueteethBindController.currentDeviceMac.value == device['mac'.tr]) { return SizedBox( width: 24.rpx, height: 24.rpx, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( stringToColor("#929699"), ), ), ); } return Container(); }), ], ), 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( "${device['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['bind'] == true ? "已被绑定" : "可绑定"}", style: TextStyle( fontSize: 26.rpx, color: device['bind'] == true ? stringToColor("#FF7159") : stringToColor("#6BFDAC"), ), ), ].divide(SizedBox(width: 33.rpx)), ), ].divide(SizedBox(height: 37.rpx)), ), ), CustomCard( borderRadius: 16.rpx, onTap: () async { try { //连接前暂停扫描 blueteethBindController.pauseScanning(); if (blueteethBindController.currentDeviceMac?.value != null && blueteethBindController .currentDeviceMac!.value.isNotEmpty) { if (blueteethBindController.currentDeviceMac?.value != device['mac'.tr]) { showConfirmDialog( context, Container(), "其他设备正在绑定中,是否终止其他设备绑定?".tr, onConfirm: () { blueteethBindController.currentDeviceMac.value = ""; blueteethBindController.updateAll(); }, onCancel: () { blueteethBindController.resumeScanning(); }); } } blueteethBindController.currentDeviceMac?.value = device['mac'.tr]!; blueteethBindController.updateAll(); if (device['bind'] == true) { await showHaveBindDialog(context); blueteethBindController.currentDeviceMac.value = ""; blueteethBindController.resumeScanning(); } else { showConfirmDialog( context, Container(), '是否确认绑定?'.tr, onConfirm: () async { //连接蓝牙 ,获取设备信息 blueteethBindController.currentDeviceMac.value = widget.bleDevice.mac; blueteethBindController.updateAll(); String mac = await getBindTHMAC( context, widget.bleDevice, widget.deviceType); if (mac != null && mac.isNotEmpty) { bool flag = await fillTHMac(mac, widget.bleDevice, context); if (!flag) { return; } } blueteethBindController.currentFullDevice = widget.bleDevice; ApiResponse response = await blueteethBindController .bindDeviceAndMAC(widget.bleDevice, context); TopSlideNotification.show(context, text: response.msg!); if (response.code == HttpStatusCodes.ok) { //关闭闹钟和打鼾干预 // resetLastDeviceConfig(widget.bleDevice); homeController.getPersonList(); //请求绑定设备列表 // homeController.getSleepReport(); homeController.getDeviceNum().then((apiResponse) { if (apiResponse.code != HttpStatusCodes.ok) { TopSlideNotification.show( Get.context!, text: apiResponse.msg!, textColor: themeController.currentColor.sc9, ); } }); homeController .getDeviceList(group: 'room') .then((apiResponse) { if (apiResponse.code != HttpStatusCodes.ok) { try { WebviewTestController webviewTestController = Get.find(); webviewTestController.web.jsbridge?.dart .alterDevice(); } catch (e) { ef.log("[h5]通知列表更新报错:$e"); } TopSlideNotification.show( Get.context!, text: apiResponse.msg!, textColor: themeController.currentColor.sc9, ); } else { //请求睡眠报告 // deviceController.getSleepReport(); } }); //更新设备绑定流程 Get.toNamed("/mHTwifiPage", arguments: widget.bleDevice); THapp bledevice = THapp( device: widget.bleDevice.scanResult.device); blueteethBindController.currentDevice = bledevice; blueteethBindController.currentDeviceMac.value = ""; WidgetsBinding.instance.addPostFrameCallback((_) { if (homeController .homeSleepDays.value.isNotEmpty) { homeController.selectedDayIndex.value = homeController.homeSleepDays.value.length - 1; } }); await homeController.getPersonList(); } else { blueteethBindController.resumeScanning(); blueteethBindController.currentDeviceMac.value = ""; blueteethBindController.updateAll(); TopSlideNotification.show( context, text: response.msg ?? "蓝牙绑定.连接异常".tr, textColor: themeController.currentColor.sc9, ); } blueteethBindController.resumeScanning(); }, onCancel: () { print('用户点击了取消'.tr); blueteethBindController.currentDeviceMac.value = ""; blueteethBindController.resumeScanning(); blueteethBindController.updateAll(); }, ); } } catch (e) { Navigator.pop(context); TopSlideNotification.show( context, text: "连接异常".tr, textColor: themeController.currentColor.sc9, ); edm.EasyDartModule.logger.info("连接异常: $e"); DailyLogUtils.writeLog("连接异常: $e"); } finally { // 确保在任何情况下都恢复扫描 // if (blueteethBindController // .currentDeviceMac.value.isEmpty) { // blueteethBindController.resumeScanning(); // } blueteethBindController.resumeScanning(); } }, colors: [stringToColor("1592AA"), stringToColor("006FA3")], child: Container( width: 150.rpx, height: 90.rpx, alignment: Alignment.center, child: Text( "添加".tr, style: TextStyle(color: Colors.white, fontSize: 26.rpx), ), ), ), ], ), ].divide(SizedBox(height: 37.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; } } // Future getBindTHMAC( // BuildContext context, BlueToothDataModel device, Map deviceType) async { // const int maxRetries = 2; // 重试2次 // const Duration timeout = Duration(seconds: 5); // String? macAddress; // for (int attempt = 1; attempt <= maxRetries; attempt++) { // 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)); // // 根据设备类型获取 MAC // 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) { // print('MAC地址: $macAddress'); // return macAddress; // 成功获取直接返回 // } // // 当前尝试失败,准备重试 // DailyLogUtils.printLog("第$attempt次尝试未获取到MAC地址"); // if (attempt < maxRetries) { // await Future.delayed(Duration(seconds: 1)); // 等待后重试 // } // } catch (e) { // DailyLogUtils.printLog("第$attempt次蓝牙获取MAC失败: $e"); // edm.EasyDartModule.logger.error("第$attempt次蓝牙获取MAC失败: $e"); // // 最后一次尝试失败后才提示 // if (attempt == maxRetries) { // blueteethBindController.currentDeviceMac.value = ""; // TopSlideNotification.show( // context, // text: "获取不到传感器mac,请重试".tr, // textColor: themeController.currentColor.sc9, // ); // rethrow; // 抛出最后的异常 // } // // 否则继续尝试 // } // } // // 正常不会到这里 // throw Exception("未知错误"); // } 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); } //重置设备闹钟数据和打鼾干预 // void resetLastDeviceConfig(BlueToothDataModel bleDevice) { // int deviceType = bleDevice.type; // if (deviceType == 2) { // //床垫 // service = MattressControlService((pkg) async { // var bytes = pkg.toBytes(); // String serviceCode = ""; // if (bleDevice != null) { // if (deviceType == DeviceType.smartBed.value) { // serviceCode = "ffe0/ffe1"; // } // if (deviceType == DeviceType.smartMattress.value) { // serviceCode = "fff0/fff2"; // } // } // if (bytes[7] != 0x00) { // //打印bytes // String hex = bytes.toString(); // print("--------->" + hex); // String hexString = bytes // .map((e) => e.toRadixString(16).padLeft(2, '0')) // .join(' ') // .toUpperCase(); // print("--------->hex->" + hexString); // } // int aa = await blueteethBindController.currentDevice! // .write(bleDevice.mac, serviceCode, bytes, withresponse: true); // ef.log(bytes.toString()); // return aa == 0 ? true : false; // }); // } else if (deviceType == 3) { // //床 // bedService = BedControlService(_writeToDevice); // 初始化 // } // } // Future _writeToDevice(Uint8List pkg) async { // int result = await bleToolController.ble // .write(macAddress.value, "ffe0/ffe1", pkg, withresponse: true); // return result == 0; // } } 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(); }