import 'dart:async'; import 'dart:collection'; import 'dart:convert'; import 'dart:core'; import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; import 'package:broadcast/broadcast.dart'; import 'package:buffer/buffer.dart'; import 'package:device_info/device_info.dart'; import 'package:flutter/material.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'package:get_it/get_it.dart'; import 'package:package_info/package_info.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sport/application.dart'; import 'package:sport/bean/hardware.dart'; import 'package:sport/db/bluetooth_db.dart'; import 'package:sport/db/step_db.dart'; import 'package:sport/pages/my/dfu_update_page.dart'; import 'package:sport/services/Converter.dart'; import 'package:sport/services/api/inject_api.dart'; import 'package:sport/services/api/rest_client.dart'; import 'package:sport/utils/toast.dart'; import 'package:sport/utils/version.dart'; import 'package:sport/widgets/dialog/alert_dialog.dart'; const BLE_CODE_0 = 0x00; const BLE_CODE_1 = 0x01; class BLE { BLE._(); static const BLE_Client_T_MOTION = 0x04; static const BLE_Client_T_UPDATE = 0xA1; static const BLE_Client_T_GAMEMODE = 0xA2; static const BLE_Client_T_CONNET_R = 0xA3; static const BLE_Client_T_SHOCK = 0xA4; static const BLE_Client_T_REALTIMESTEP = 0xA5; static const BLE_Client_T_DFU = 0xA6; static const BLE_Client_T_CHARGE = 0xA7; static const BLE_Client_T_LIGHTING = 0xAC; static const BLE_Client_T_ERR = 0xAD; } class BLE_UPDATE { BLE_UPDATE._(); static const BASIC = 0x00; static const DATA = 0x01; static const STEP = 0x02; static const STEP_DELETE = 0x03; } const BLE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"; class Bluetooth with ChangeNotifier, InjectApi { static final String PREF_KEY = "SHOES"; factory Bluetooth() => _getInstance(); static Bluetooth? _instance; // 获取对象 static Bluetooth _getInstance() { if (_instance == null) { // 使用私有的构造方法来创建对象 _instance = Bluetooth._internal(); } return _instance!; } Bluetooth._internal() { //初始化(设置一些默认的)... } final List> _streamSubscriptions = >[]; void addSubscription(StreamSubscription streamSubscription) { _streamSubscriptions.add(streamSubscription); } void disposeSubscription() { for (StreamSubscription subscription in _streamSubscriptions) { subscription.cancel(); } _streamSubscriptions.clear(); characteristicNotify?.cancel(); } // ignore: close_sinks final StreamController _queryController = StreamController.broadcast(); Stream get queryStream => _queryController.stream; // ignore: close_sinks final StreamController _stateController = StreamController.broadcast(); Stream get stateStream => _stateController.stream; // ignore: close_sinks final StreamController _dataController = StreamController.broadcast(); Stream get dataStream => _dataController.stream; // ignore: close_sinks final StreamController _connectRightController = StreamController.broadcast(); Stream get connectRightStream => _connectRightController.stream; final ValueNotifier deviceNotifier = ValueNotifier(null); final ValueNotifier infoNotifier = ValueNotifier({}); final ValueNotifier versionNotifier = ValueNotifier(""); final ValueNotifier dataNotifier = ValueNotifier({}); final ValueNotifier electricityNotifier = ValueNotifier(0); final ValueNotifier stepNotifier = ValueNotifier(0); final ValueNotifier stepTotalNotifier = ValueNotifier(0); final ValueNotifier stepTotalTestNotifier = ValueNotifier(0); final ValueNotifier actionNotifier = ValueNotifier(0); final ValueNotifier> byteNotifier = ValueNotifier>([]); final ValueNotifier vibrateNotifier = ValueNotifier(400); final ValueNotifier stepRealtimeNotifier = ValueNotifier(-1); final ValueNotifier stepRealtimePageNotifier = ValueNotifier(false); final ValueNotifier verNotifier = ValueNotifier(null); final ValueNotifier> stateNotifier = ValueNotifier({1: 0, 0: 0}); final ValueNotifier h5gameRNotifier = ValueNotifier(false); final ValueNotifier deviceWriteNotifier = ValueNotifier(false); final ValueNotifier deviceReadNotifier = ValueNotifier(false); final ValueNotifier deviceReadNotifyNotifier = ValueNotifier(false); final ValueNotifier rssiNotifier = ValueNotifier(0); final ValueNotifier> timeUseNotifier = ValueNotifier>({}); DiscoveredDevice? _connectingDevice, _connectedDevice; bool _listen = false; bool _online = false; bool _uploadLoged = false; String? _deviceId; PartInfo? _partInfo; Completer? _completerStep; Completer? _completerStepDel; Completer? _completerSetupDeviceVer; StreamSubscription? scanStreamSubscription; QualifiedCharacteristic? characteristicWrite; StreamSubscription? characteristicNotify; late FlutterReactiveBle flutterReactiveBle; Timer? stepRealTimeTimer; Timer? gameModeTimer; Timer? timerConnected; Timer? rssiTimer; Timer? disconnectTimer; int disconnectTimes = 0; DateTime? startTime; List timeUse = []; int testDfu = 0; int _isRightError = 0; bool get isConnected => _online; bool get isNotReady => isDebugShoe ? false : !(_online == true && electricityNotifier.value > 0); bool get isRightError => _isRightError != 1; late BuildContext _context; bool _showChargeDialog = false; DateTime? electricityTime; resetData() { stepNotifier.value = 0; stepTotalNotifier.value = 0; actionNotifier.value = 0; } DiscoveredDevice? get device => _connectedDevice; set device(DiscoveredDevice? device) { _connectedDevice = device; notifyListeners(); } bool _dfu = false; set dfu(bool dfu) { _dfu = dfu; } Future _saveDevice(DiscoveredDevice device) async { var id = device.id.toString(); var prefs = await SharedPreferences.getInstance(); prefs.setString(PREF_KEY, id); return id; } clearDevice({bool deleteStep = false}) async { var prefs = await SharedPreferences.getInstance(); prefs.remove(PREF_KEY); if (deleteStep == true) { var db = StepDB(); db.deleteAll(); } startTime = null; await disconnectDevice("清空设备"); this.device = _connectedDevice = null; } Future getHistoryDevice() async { var prefs = await SharedPreferences.getInstance(); return prefs.getString(PREF_KEY); } init(BuildContext context) async { this._context = context; this._listen = true; if (Platform.isAndroid) { DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); AndroidDeviceInfo info = await deviceInfo.androidInfo; if (info.version.sdkInt < 31) { if (await Permission.locationWhenInUse.status.isGranted != true) return; } else { Map statuses = await [ Permission.bluetoothScan, Permission.bluetoothConnect, ].request(); if (statuses.isNotEmpty == true && statuses.values.any((element) => element.isGranted) != true) { return; } } } flutterReactiveBle = FlutterReactiveBle(); flutterReactiveBle.statusStream.listen((state) { print("bluetooth -- instance state $state"); if (state == BleStatus.ready) { connectDevice(); } else if (state == BleStatus.unknown) { } else if (state == BleStatus.poweredOff) { disconnectDevice("蓝牙状态不可用"); ToastUtil.show("蓝牙服务已关闭"); } else {} }); flutterReactiveBle.connectedDeviceStream.listen((event) { print("bluetooth -- state $event!"); if (event.connectionState == DeviceConnectionState.connected) { if (_deviceId == event.deviceId) { final deviceId = _deviceId!; notifyConnectTime("C"); discoverServices(flutterReactiveBle, deviceId).then((value) { notifyConnectTime("D$value"); if (value == 2) { setupGameMode(h5gameRNotifier.value); queryDeviceRight(); timerConnected = Timer(Duration(seconds: 30), () { if (isRightError) clearDevice(); }); } else {} }); } } else if (event.connectionState == DeviceConnectionState.disconnected) { if (event.failure?.code == ConnectionError.failedToConnect) { disconnectDevice("${event.failure?.message}"); if (event.failure?.message?.contains("133") == true) { Future.microtask(() { connectDevice(id: event.deviceId); }); } } } }); versionNotifier.addListener(() { if (versionNotifier.value.isNotEmpty) { checkUpdate(); } }); connectRightStream.listen((event) { print("bluetooth -- connect right ${_connectingDevice?.name} ${event}!"); if (event == 1 && _isRightError != event) { notifyConnectTime("R"); timerConnected?.cancel(); dataNotifier.value = {}; infoNotifier.value = {}; verNotifier.value = null; if (_connectingDevice != null) { _saveDevice(_connectingDevice!); _connected(_connectingDevice!); _query(); } } _isRightError = event; }); } Future listen(BuildContext context, {bool init = false}) async { this._context = context; this._listen = true; print("bluetooth -- listen"); Broadcast.broadcast("SHOE.SDK.BLUE_DISCONNECT"); var prefs = await SharedPreferences.getInstance(); if (!prefs.containsKey("token")) { return; } disconnectTimer?.cancel(); disconnectTimes = 0; if (_dfu == true) return; if (!isConnected) { connectDevice(); } } void runTimer() { addSubscription(Stream.periodic(Duration(seconds: 60)).listen((event) async { if (device == null) return; if (!_online) return; await queryDeviceData(); await queryDeviceStep(); })); } Future background(BuildContext context) async { this._listen = false; if (_showChargeDialog == true) { Navigator.maybePop(context); } disconnectTimer?.cancel(); disconnectTimer = Timer.periodic(Duration(seconds: 5), (t) { disconnectTimes++; if (disconnectTimes >= 20) { disconnectDevice("后台时断开连接"); t.cancel(); } }); } Future disposeBluetooth(BuildContext context) async { if (_dfu == true) return; // // 实时计步中不主动断开 if (stepRealtimePageNotifier.value == true) return; gameModeTimer?.cancel(); this._listen = false; if (_showChargeDialog == true) { Navigator.maybePop(context); } // if (Platform.isIOS) { await disconnectDevice("app退后台"); // } } resetNotifierData() { dataNotifier.value = {}; infoNotifier.value = {}; verNotifier.value = null; electricityNotifier.value = -1; _connectRightController.add(0); stateNotifier.value = {0: 0, 1: 0}; deviceWriteNotifier.value = false; deviceReadNotifier.value = false; deviceReadNotifyNotifier.value = false; _online = false; _stateController.add(false); } Future disconnectDevice(String msg) async { print("bluetooth -- disconnect $msg $device"); _deviceId = null; disposeSubscription(); deviceNotifier.value = null; timerConnected?.cancel(); resetNotifierData(); disconnectTimes = 0; _uploadLoged = false; device = _connectedDevice = _connectingDevice = null; errQueue.clear(); } Future connectDevice({DiscoveredDevice? device, String? id, bool again = false}) async { await disconnectDevice("主动连接"); if (again != true) { timeUse = []; timeUseNotifier.value = {}; } if (device != null) { startTime = null; BluetoothDB().insert(device); } notifyConnectTime("F"); String? deviceId = id; if (device == null) { if (deviceId == null) { deviceId = await getHistoryDevice(); } print("bluetooth -- connect from $deviceId $device"); if (deviceId == null || deviceId.isEmpty) { return null; } try { device = await discoverDevice(deviceId: deviceId); } catch (e) { print(e); return; } } if (device == null) return; _deviceId = deviceId = device.id; print("bluetooth -- connect $deviceId!"); notifyConnectTime("S"); addSubscription(flutterReactiveBle .connectToDevice( id: deviceId, connectionTimeout: const Duration(seconds: 8), ) .listen((update) { _connectingDevice = device!; // print("bluetooth -- connect state $deviceId $update!"); }, onError: (Object e) { print("bluetooth -- connect error $deviceId $e!"); _connectingDevice = null; })); } Future discoverDevice({String? deviceId, String? deviceName}) async { // 2022-04-26 11:10:53.285 17454-17479/com.ouj.sdk.test I/System.out: 00001800-0000-1000-8000-00805f9b34fb // 2022-04-26 11:10:53.285 17454-17479/com.ouj.sdk.test I/System.out: 00002a00-0000-1000-8000-00805f9b34fb // 2022-04-26 11:10:53.285 17454-17479/com.ouj.sdk.test I/System.out: 00002a01-0000-1000-8000-00805f9b34fb // 2022-04-26 11:10:53.285 17454-17479/com.ouj.sdk.test I/System.out: 00002a04-0000-1000-8000-00805f9b34fb // 2022-04-26 11:10:53.285 17454-17479/com.ouj.sdk.test I/System.out: 00002aa6-0000-1000-8000-00805f9b34fb // 2022-04-26 11:10:53.285 17454-17479/com.ouj.sdk.test I/System.out: 00001801-0000-1000-8000-00805f9b34fb // 2022-04-26 11:10:53.285 17454-17479/com.ouj.sdk.test I/System.out: 6e400001-b5a3-f393-e0a9-e50e24dcca9e // 2022-04-26 11:10:53.285 17454-17479/com.ouj.sdk.test I/System.out: 6e400002-b5a3-f393-e0a9-e50e24dcca9e // 2022-04-26 11:10:53.285 17454-17479/com.ouj.sdk.test D/BluetoothController: discovered characteristic write android.bluetooth.BluetoothGatt@28bbf97 android.bluetooth.BluetoothGattCharacteristic@81cd69e // 2022-04-26 11:10:53.285 17454-17479/com.ouj.sdk.test I/System.out: 6e400003-b5a3-f393-e0a9-e50e24dcca9e // 2022-04-26 11:10:53.285 17454-17479/com.ouj.sdk.test D/BluetoothGatt: setCharacteristicNotification() - uuid: 6e400003-b5a3-f393-e0a9-e50e24dcca9e enable: true // 2022-04-26 11:10:53.286 17454-17479/com.ouj.sdk.test D/BluetoothController: discovered characteristic notify android.bluetooth.BluetoothGatt@28bbf97 android.bluetooth.BluetoothGattCharacteristic@9dfb57f // 2022-04-26 11:10:53.286 17454-17479/com.ouj.sdk.test I/System.out: 0000fe59-0000-1000-8000-00805f9b34fb // 2022-04-26 11:10:53.286 17454-17479/com.ouj.sdk.test I/System.out: 8ec90003-f315-4f60-9fb8-838830daea50 scanStreamSubscription?.cancel(); final completer = Completer(); Timer timeout = Timer(Duration(seconds: 20), () { completer.completeError("timeout"); }); scanStreamSubscription = flutterReactiveBle.scanForDevices(withServices: [Uuid.parse(BLE_UUID)]).listen((event) { print('bluetooth -- scan device : ${event.id} ${event.name}'); if (event.id == deviceId || event.name == deviceName || (deviceName != null && event.name.contains(deviceName))) { if (completer.isCompleted != true) completer.complete(event); } }); var device; try { device = await completer.future.catchError((error, stackTrace) => throw error); timeout.cancel(); } catch (e) { print("bluetooth -- shoe right fail $e"); print(e); } finally { scanStreamSubscription?.cancel(); } print('bluetooth -- found: ${device?.id} ${device?.name}'); return device; } Future discoverServices(FlutterReactiveBle flutterReactiveBle, String deviceId) async { int result = 0; characteristicNotify?.cancel(); for (var i = 0; i < 3; i++) { bool findWrite = false; bool findRead = false; try { var services = await flutterReactiveBle.discoverServices(deviceId); for (DiscoveredService service in services) { for (var characteristic in service.characteristics) { if (characteristic.isWritableWithoutResponse) { characteristicWrite = QualifiedCharacteristic(serviceId: service.serviceId, characteristicId: characteristic.characteristicId, deviceId: deviceId); findWrite = true; deviceWriteNotifier.value = true; } if (characteristic.isNotifiable) { deviceReadNotifier.value = true; findRead = true; final qualifiedCharacteristic = QualifiedCharacteristic(serviceId: service.serviceId, characteristicId: characteristic.characteristicId, deviceId: deviceId); characteristicNotify = flutterReactiveBle.subscribeToCharacteristic(qualifiedCharacteristic).listen((event) { deviceReadNotifyNotifier.value = true; if (this._listen == true) { byteNotifier.value = event; parse(event); } }); } } } } catch (e) { print(e); print("bluetooth -- service error $e"); characteristicNotify?.cancel(); } if (findWrite && findRead) { print("bluetooth -- service write: $findWrite, read: $findRead"); result = 2; break; } else { await Future.delayed(Duration(seconds: 2)); } } return result; } _query() async { queryDeviceInfo(); queryDeviceRight(); queryDeviceStep(); queryDeviceCharge(); queryDeviceData(); } _connected(DiscoveredDevice newDevice) async { _stateController.add(true); _online = true; deviceNotifier.value = newDevice; this.device = newDevice; if (isDebugShoe) { ToastUtil.show("${newDevice.name} 已连接"); } if ((startTime?.difference(DateTime.now()).inHours ?? 1) > 0) { notifyConnected(); startTime = DateTime.now(); } runTimer(); flutterReactiveBle.connectedDeviceStream.toList().then((value) { value.forEach((element) { if (element.deviceId != newDevice.id) flutterReactiveBle.clearGattCache(element.deviceId); }); }); } notifyConnectTime(String tag) { int now = DateTime.now().millisecondsSinceEpoch; Map result = timeUseNotifier.value; // print("111111111111111111 ${now} ${timeUse}"); result[tag] = now - (timeUse.isNotEmpty ? timeUse.last : now); timeUseNotifier.value = result; timeUse.add(now); } notifyConnected() { this.vibrate(200); this.lighting(10000); } checkUpdate() async { var info = infoNotifier.value; print("bluetooth -- checkUpdate ${info}"); if (info.isEmpty == true) return; String softwareVer = info['softwareVer']; String hardwareVer = info['hardwareVer']; List device = info['device']; PackageInfo packageInfo = await PackageInfo.fromPlatform(); String appVer = packageInfo.version; Hardware? updateInfo = (await GetIt.I().checkHardwareUpdate(hardwareVer, appVer)).data; if (updateInfo != null) { // PackageInfo packageInfo = await PackageInfo.fromPlatform(); // if (updateInfo.min_app_ver?.isNotEmpty == true && versionCompare(packageInfo.version, updateInfo.min_app_ver ?? "0") < 0) { // debugPrint("bluetooth -- dfu update app ver ${packageInfo.version} min ${updateInfo.min_app_ver}"); // return; // } // if (updateInfo.max_app_ver?.isNotEmpty == true && versionCompare(packageInfo.version, updateInfo.max_app_ver ?? "0") > 0) { // debugPrint("bluetooth -- dfu update app ver ${packageInfo.version} max ${updateInfo.max_app_ver}"); // return; // } // // if (updateInfo.min_hardware_ver?.isNotEmpty == true && versionCompare(hardwareVer, updateInfo.min_hardware_ver ?? "0") < 0) { // debugPrint("bluetooth -- dfu update hardware ver ${hardwareVer} min ${updateInfo.min_hardware_ver}"); // return; // } // // if (updateInfo.max_hardware_ver?.isNotEmpty == true && versionCompare(hardwareVer, updateInfo.max_hardware_ver ?? "0") > 0) { // debugPrint("bluetooth -- dfu update hardware ver ${hardwareVer} max ${updateInfo.max_hardware_ver}"); // return; // } updateInfo.devices = []; updateInfo.localVer = softwareVer; print("bluetooth -- dfu versionCompare ${versionCompare(softwareVer, updateInfo.ver ?? "0.0")}"); bool cancelable = this.testDfu >= 5 ? true : Platform.isAndroid ? !inProduction : false; if (cancelable == false) { if (versionCompare(softwareVer, updateInfo.ver ?? "0.0") >= 0) { return; } } for (var i = device.length - 1; i >= 0; i--) { updateInfo.devices!.add(device[i]); } verNotifier.value = updateInfo; if (isDebugShoe) { var context = _context; var value = updateInfo; var closeable = 0; var result = await showDialog( context: context, barrierDismissible: false, builder: (context) => CustomAlertDialog( title: '发现新的鞋子固件', child: WillPopScope( onWillPop: () async { return false; }, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { closeable++; if (closeable >= 5) { Navigator.of(context).pop(false); } }, child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 24.0), child: Text( "${value.name}\n${value.localVer} -> ${value.ver}\n${value.msg}", style: TextStyle(fontSize: 14, color: Color(0xff333333), height: 1.8), )), ), ), textOk: '立即升级', cancelable: cancelable, ok: () => Navigator.of(context).pop(true)), ); if (result != true) return; result = await showDialog( context: context, barrierDismissible: false, builder: (context) => SimpleDialog( children: [ DfuUpdatePage( hardware: value, ) ], )); } } } void parse(List event) { if (event.isEmpty == true) return; if (!_listen) return; ByteDataReader reader = ByteDataReader(); reader.add(event); int flagAA = reader.readUint8(); // int flagBB = reader.readUint8(); // int flagCC = reader.readUint8(); int len = reader.readUint8(); int len1 = 0xFF - reader.readUint8(); // int index = reader.readUint8(); int cmd = reader.readUint8(); // print(" change --> ${flagAA} ${len} ${~len} ${cmd}"); // print(" change --> ${len} ${~len} ${len1}"); // print(" change --> ${index} ${cmd}"); if (!(flagAA == 0xAA)) return; if (len != len1) return; Uint8List byteArray = reader.read(event.length - 5); int ver = 0; for (int i = 0; i < event.length - 1; i++) { ver += event[i]; } if (ver.toUnsigned(8) != event[event.length - 1]) return; parseCmd(cmd, byteArray); } void parseCmd(int cmd, Uint8List byteArray) { // if (isDebugShoe) print(" cmd $cmd data --> $byteArray"); switch (cmd) { case 0x01: _parseAction(byteArray); break; case BLE.BLE_Client_T_MOTION: disconnectTimes = 0; _dataController.add(byteArray); break; case 0xA0: break; case BLE.BLE_Client_T_UPDATE: _parseQuery(byteArray); break; case BLE.BLE_Client_T_CONNET_R: _connectRightController.add(byteArray.first); break; case BLE.BLE_Client_T_REALTIMESTEP: _parseStepRealtime(byteArray); break; case BLE.BLE_Client_T_DFU: if (_completerSetupDeviceVer?.isCompleted != true) _completerSetupDeviceVer?.complete(true); break; case BLE.BLE_Client_T_CHARGE: //充电 _parseCharge(byteArray); break; case BLE.BLE_Client_T_ERR: //异常 _parseErr(byteArray); break; case BLE.BLE_Client_T_GAMEMODE: break; } } void _parseAction(Uint8List byteArray) { ByteDataReader reader = ByteDataReader(); reader.add(byteArray); int action = reader.readUint8(); int t = reader.readUint16(); print("action: $action "); actionNotifier.value = action; } void _parseQuery(Uint8List byteArray) { ByteDataReader reader = ByteDataReader(); reader.add(byteArray); int cmd = reader.readUint8(); switch (cmd) { case BLE_UPDATE.BASIC: // 设备基本信息 Map info = {}; List name = []; for (var i = 0; i < 64; i++) { name.add(reader.readUint8()); } info['name'] = String.fromCharCodes(name).replaceAll("\u0000", ""); List softwareVer = []; List hardwareVer = []; List device = []; for (var i = 0; i < 2; i++) { List macList = []; for (var j = 0; j < 6; j++) { macList.add(reader.readUint8().toRadixString(16).padLeft(2, "0")); } var mac = macList.join(":").toUpperCase(); print("mac $i = $mac"); for (var k = 0; k < 4; k++) { hardwareVer.add(reader.readUint8()); } softwareVer.add(reader.readUint8()); device.add(mac); } print("bluetooth dfu info $hardwareVer $softwareVer"); if (hardwareVer.length != 8 || softwareVer.length != 2) return; String leftHardware = hardwareVer.sublist(1, 4).join("."); String rightHardware = hardwareVer.sublist(4 + 1, hardwareVer.length).join("."); int compareHardware = versionCompare(leftHardware, rightHardware); String minHardwareVersion = compareHardware < 0 ? leftHardware : rightHardware; print("bluetooth hardwareVer $leftHardware $rightHardware $compareHardware $minHardwareVersion"); String leftSoftware = hardwareVer.sublist(1, 3).join(".") + ".${softwareVer[0]}"; String rightSoftware = hardwareVer.sublist(4 + 1, 4 + 1 + 2).join(".") + ".${softwareVer[0]}"; int compareSoftware = versionCompare(leftSoftware, rightSoftware); String minSoftwareVersion = compareSoftware < 0 ? leftSoftware : rightSoftware; print("bluetooth softwareVer $leftSoftware $rightSoftware $compareSoftware $minSoftwareVersion"); shoeVersion = "$minHardwareVersion"; info['softwareVer'] = minSoftwareVersion; info['hardwareVer'] = minHardwareVersion; info['mac'] = device.length > 1 ? device.first : ""; info['device'] = device; infoNotifier.value = info; if (versionNotifier.value != minSoftwareVersion) versionNotifier.value = minSoftwareVersion; _uploadErr(); if (_uploadLoged != true) { _uploadLog(); } break; case BLE_UPDATE.DATA: // 设备数据(左鞋,右鞋) Map info = {}; for (var i = 0; i < 2; i++) { info['${i}_electricity'] = reader.readUint8(); info['${i}_temperature'] = reader.readUint8(); info['${i}_pressure'] = reader.readUint(4); reader.readUint(4); } if (reader.remainingLength >= 4) { info['0_adc'] = reader.readUint16(); info['1_adc'] = reader.readUint16(); } // [170, 187, 204, 23, 232, 0, 161, 1, 50, 0, 33, 0, 0, 156, 41, 51, 0, 34, 0, 0, 150, 19, 232] dataNotifier.value = info; int electricity = min(info['0_electricity'] ?? 0, info['1_electricity'] ?? 0); electricityNotifier.value = electricity; if (electricity < 10) { if (electricityTime == null || (electricityTime?.difference(DateTime.now()).inMinutes.abs() ?? 0) >= 60) { if (_showChargeDialog != true) { ToastUtil.show("鞋子电量不足,请及时充电!"); electricityTime = DateTime.now(); } } } if (isDebugShoe) { _testElectricity(info); } break; case BLE_UPDATE.STEP: // 查询步数 print("bluetooth -- step part ${_partInfo?.ready}"); if (_partInfo == null) return; if (_partInfo?.ready == true) return; if (isDebugShoe) { _testStep(byteArray.toList().join(",")); } int start = reader.readUint64(); int serialCount = reader.readUint16(); int serial = reader.readUint16(); // if(serialCount == 0){ // _completerStep?.complete(false); // return; // } List data = []; while (reader.remainingLength > 0) { data.add(reader.readUint32()); } if (serial == 0 && data.isNotEmpty) { stepTotalNotifier.value = data.last; } _partInfo!.start = start; _partInfo!.serialCount = serialCount + 1; _partInfo!.data[serial] = data; print("bluetooth -- step start $start read ($serial,$serialCount) --> $data"); if (_partInfo!.data.length == _partInfo!.serialCount) { _partInfo!.ready = true; var partResult = _partInfo!; Future.microtask(() async { await partResult.prepare(); print("bluetooth -- step end ... "); await _saveInfo(partResult); }); if (_completerStep?.isCompleted != true) _completerStep?.complete(true); } break; case BLE_UPDATE.STEP_DELETE: // 步数回调 if (_completerStepDel != null) _completerStepDel!.complete(true); break; } } void _parseStepRealtime(Uint8List byteArray) { ByteDataReader reader = ByteDataReader(); reader.add(byteArray); int cmd = reader.readUint8(); if (cmd == 0) { } else if (cmd == 1) { int left = 0; int right = 0; if (reader.remainingLength > 4) { left = reader.readUint32(); right = reader.readUint32(); } else { left = reader.readUint16(); right = reader.readUint16(); } int step = left + right; // print("left: ${left}, right: ${ right} = ${step}"); stepRealtimeNotifier.value = step; // 已经不是在实时计步页面,但还收到数据,关了! if (stepRealtimePageNotifier.value == false) { write(BLE.BLE_Client_T_REALTIMESTEP, Uint8List.fromList([BLE_CODE_0])); } } } void _parseCharge(Uint8List byteArray) async { ByteDataReader reader = ByteDataReader(); reader.add(byteArray); int cmd = reader.readUint8(); int state = reader.readUint8(); Map _state = Map.from(stateNotifier.value); _state.update(cmd, (value) => state); stateNotifier.value = _state; // print("111111111111111111111111 $_state"); if (_state[0]! <= 1 && _state[1]! <= 1) { if (_showChargeDialog == true) { Navigator.maybePop(_context); } return; } if (_state[0] == 3 && _state[1] == 3) { queryDeviceData(); } final String _name = "dialog_charge"; if (_showChargeDialog == true) return; _showChargeDialog = true; showDialog( context: _context, barrierColor: Colors.transparent, builder: (context) => GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { Navigator.maybePop(context); }, child: Scaffold( backgroundColor: Colors.transparent, body: Center( child: Container( constraints: BoxConstraints(maxWidth: 180.0), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(10.0)), color: Colors.black.withOpacity(.75), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Align( child: GestureDetector( child: Padding( padding: const EdgeInsets.all(10.0), child: Image.asset( "lib/assets/img/btn_close_white.png", width: 18, ), ), onTap: () { Navigator.maybePop(context); }, behavior: HitTestBehavior.opaque, ), alignment: Alignment.topRight, ), Padding( padding: const EdgeInsets.fromLTRB(20.0, 5, 20.0, 16.0), child: ValueListenableBuilder>( valueListenable: stateNotifier, builder: (context, val, __) { int totalState = (val[0]! + val[1]!) ~/ 2; List keys = val.keys.toList(); keys.sort(); return Column( children: [ Center( child: Row( children: keys.map((key) { int state = val[key]!; String label = key == 0 ? "left" : "right"; return Padding( padding: EdgeInsets.symmetric(horizontal: 10), child: Image.asset( state == 3 ? "lib/assets/img/pop_icon_${label}full.png" : state == 2 ? "lib/assets/img/pop_icon_${label}charge.png" : "lib/assets/img/pop_icon_${label}fault.png", ), ); }).toList(), mainAxisSize: MainAxisSize.min, ), ), const SizedBox( height: 15, ), ValueListenableBuilder( valueListenable: electricityNotifier, builder: (BuildContext context, int value, Widget? child) { int _power = max(0, (val[0]! > 1 && val[1]! > 1) ? value : 0); return Column( children: [ Text( (_power > 1) ? "$_power%" : "", style: TextStyle(fontSize: 8.0, color: Colors.white), ), const SizedBox( height: 7, ), ClipRRect( child: SizedBox( height: 2, child: LinearProgressIndicator( backgroundColor: Color(0xff656565), valueColor: AlwaysStoppedAnimation(Color(0xff00DC42)), value: _power / 100, ), ), borderRadius: BorderRadius.circular(5), ) ], ); }, ), const SizedBox( height: 11, ), Text( (val[0] == 3 && val[1] == 3) ? "智能鞋充电已完成" : (val[0]! > 1 && val[1]! > 1) ? "智能鞋充电中..." : (val[0]! <= 1 && val[1]! <= 1) ? "智能鞋连接异常" : val[0]! <= 1 ? "左鞋充电连接异常" : val[1]! <= 1 ? "右鞋充电连接异常" : "连接异常", style: TextStyle(fontSize: 12.0, color: Colors.white), ), ], ); }), ), ], )), ), ), ), routeSettings: RouteSettings(name: _name)) .then((value) => _showChargeDialog = false); await Future.delayed(Duration(seconds: 5)); if (_showChargeDialog == true) { Map val = stateNotifier.value; print("_parseCharge $val"); if (val[0]! > 1 && val[1]! > 1) { Navigator.maybePop(_context); } } } Queue> errQueue = Queue(); /** * 错误处理 */ void _parseErr(Uint8List byteArray) async { ByteDataReader r = ByteDataReader(); r.add(byteArray); int leftOrRight = r.readUint8(); int code = r.readUint8(); List msgData = r.read(r.remainingLength); String msg = String.fromCharCodes(msgData); print("bluetooth - _parseErr $leftOrRight $code $msg"); var err = {"code": "$code", "msg": "${leftOrRight == 0 ? "左鞋" : "右鞋"}:$msg"}; if (errQueue.where((element) => element["code"] == "$code").isEmpty) errQueue.addLast(err); if (isDebugShoe) { await _testErrLog(); } _uploadErr(); } void _uploadErr() async { if (infoNotifier.value.isEmpty) return; if (errQueue.isEmpty) return; var info = infoNotifier.value; PackageInfo packageInfo = await PackageInfo.fromPlatform(); String mac = "${info['mac']}"; String firmware = "${info['softwareVer']}"; String hardware = "${info['hardwareVer']}"; String app_ver = "${packageInfo.version}"; while (errQueue.isNotEmpty) { var first = errQueue.removeFirst(); var err_code = first['code'] as String; var err_msg = first['msg'] as String; var resp = await api.logHardwareErr(mac, firmware, hardware, app_ver, err_code, err_msg, ""); if (resp.code != 0) { errQueue.addLast(first); await Future.delayed(Duration(seconds: 5)); } } } Future _saveInfo(PartInfo? partInfo) async { if (partInfo == null) return; print("bluetooth -- step save $partInfo ${partInfo.items.length}"); if (partInfo.items.isEmpty == true) return; List _items = []; for (var item in partInfo.items) { if (item.step == 0 && item.distance == 0) continue; _items.add(item); print("bluetooth -- step save ${item.time} ${item.step}"); } await StepDB().insertAll(_items); _queryController.add(true); } Future _uploadInfo(int startTime) async { var list = await StepDB().find(startTime); print("bluetooth -- step upload $startTime $list"); if (list.isEmpty) return 0; List> data = []; int step = 0; for (var item in list) { data.add([item['st'], item['di'], (item['time'] as int) ~/ 1000]); step += Converter.toInt(item['st']); } if (step > 5) { var resp = await api.addDaily(data: json.encode(data)); if (resp.code == 0) { // await StepDB().delete(last); return 0; } } return -1; // Directory appDocDir = await getApplicationDocumentsDirectory(); // String appDocPath = appDocDir.path; // Directory saveDir = Directory("$appDocPath/step"); // var files = saveDir.listSync(); // for(var file in files){ // var f= new File(file.path); // var content = f.readAsStringSync(); // await api.addDaily(data: content); // file.delete(); // } } // 查询 设备基本信息 Future queryDeviceCharge() async { await write(BLE.BLE_Client_T_CHARGE, Uint8List(0)); } // 查询 设备右鞋 Future queryDeviceRight() async { await write(BLE.BLE_Client_T_CONNET_R, Uint8List.fromList([])); } // 查询 设备基本信息 Future queryDeviceInfo() async { await write(BLE.BLE_Client_T_UPDATE, Uint8List.fromList([BLE_CODE_0])); } // 查询 设备数据 Future queryDeviceData() async { await write(BLE.BLE_Client_T_UPDATE, Uint8List.fromList([BLE_CODE_1])); } // 查询 查询步数 Future queryDeviceStep({bool test = false}) async { if (!isConnected) return; // await Future.delayed(Duration(seconds: 3)); // _queryController.add(true); if (test != true) if (stepTotalTestNotifier.value == 1) return; if (_partInfo != null) { print("bluetooth -- step _partInfo.now ${_partInfo!.now.difference(DateTime.now()).inSeconds}"); if ((_partInfo!.now.difference(DateTime.now()).inSeconds) > -60) { return; } } print("bluetooth -- step init"); _partInfo = PartInfo(0, 0); if (test == true) stepTotalNotifier.value = -1; // await write(0xA1, createTime(0x02)); await write(BLE.BLE_Client_T_UPDATE, Uint8List.fromList([BLE_UPDATE.STEP, BLE_CODE_0, BLE_CODE_0])); if (_completerStep != null && _completerStep?.isCompleted != true) _completerStep?.complete(false); if (_completerStepDel != null && _completerStepDel?.isCompleted != true) _completerStepDel?.complete(false); _completerStep = Completer(); _completerStepDel = Completer(); var timer = Timer.periodic(Duration(seconds: 1), (timer) { if (_partInfo == null) return; Iterable keys = _partInfo!.data.keys; if (keys.isEmpty) return; for (var i = 1; i < _partInfo!.serialCount; i++) { if (!keys.contains(i)) { print("bluetooth -- step request package $i"); // 请求缺包 ByteDataWriter writer = ByteDataWriter(); writer.writeUint8(BLE_UPDATE.STEP); writer.writeUint16(i); write(BLE.BLE_Client_T_UPDATE, writer.toBytes()); } } }); Timer timeout = Timer(Duration(seconds: 20), () { if (_completerStep?.isCompleted != true) _completerStep?.complete(false); }); var result = await _completerStep!.future; timeout.cancel(); timer.cancel(); int stepTotal = 0; if (result == true) { await _saveInfo(_partInfo); int code = await _uploadInfo(_partInfo!.start); if (isDebugShoe) { var now = DateTime.now(); _partInfo?.items.forEach((element) { var info = {"t": element.time, "s": element.step, "total": element.absolute}; _testStep("${now.hour},${now.minute},${now.second},${info['t']},${info['s']},${info['total']}"); }); } if (test == true) { if (_partInfo?.items.isNotEmpty == true) { stepTotal = _partInfo?.items.map((e) => e.step).reduce((value, element) => value + element) ?? 0; } else { stepTotal = 0; } } if (code == 0) { var timer = Timer.periodic(Duration(milliseconds: 1000), (timer) { write(BLE.BLE_Client_T_UPDATE, createTime(BLE_UPDATE.STEP_DELETE)); }); Timer timeout = Timer(Duration(seconds: 60), () { if (_completerStepDel?.isCompleted != true) _completerStepDel?.complete(false); }); await _completerStepDel!.future; timer.cancel(); timeout.cancel(); } _completerStepDel = null; } _partInfo = null; print("bluetooth -- step reset"); stepNotifier.value = stepTotal; } // 固件升级 Future setupDeviceVer(int type) async { _completerSetupDeviceVer = Completer(); try { write(BLE.BLE_Client_T_DFU, Uint8List.fromList([type])); } catch (e) { print(e); } Timer timeout = Timer(Duration(seconds: 10), () { if (_completerSetupDeviceVer?.isCompleted != true) _completerSetupDeviceVer?.complete(false); }); var result = (await _completerSetupDeviceVer?.future) ?? false; timeout.cancel(); return result; } // 游戏模式 Future setupGameMode(bool mode) async { if (mode == false && h5gameRNotifier.value == true) { return; } gameModeTimer?.cancel(); if (mode) { gameModeTimer = Timer.periodic(Duration(seconds: 60), (timer) { if (this._listen == true) write(BLE.BLE_Client_T_GAMEMODE, Uint8List.fromList([BLE_CODE_1])); }); } await write(BLE.BLE_Client_T_GAMEMODE, mode ? Uint8List.fromList([BLE_CODE_1]) : Uint8List.fromList([BLE_CODE_0])); } Future setupGameMode4h5(bool mode) async { h5gameRNotifier.value = mode; setupGameMode(mode); } Future setupDataDebug(bool mode) async { await write(BLE.BLE_Client_T_GAMEMODE, mode ? Uint8List.fromList([BLE_CODE_1 + 1]) : Uint8List.fromList([BLE_CODE_0])); // // device?.rssi.listen((event) { // rssiNotifier.value = event; // }); // // rssiTimer?.cancel(); // if (mode == true) { // rssiTimer = Timer.periodic(Duration(seconds: 1), (timer) { // device?.requestRssi(); // }); // } } Future vibrate(int vibrate, {int leftOrRight = 0}) async { int duration = min(1000, max(100, vibrate)); ByteDataWriter writer = ByteDataWriter(); writer.writeUint16(duration); writer.writeUint8(leftOrRight); await write(BLE.BLE_Client_T_SHOCK, writer.toBytes()); } Future lighting(int time) async { int duration = min(10000, max(100, time)); ByteDataWriter writer = ByteDataWriter(); writer.writeUint16(duration); await write(BLE.BLE_Client_T_LIGHTING, writer.toBytes()); } Future stepRealTime(bool mode) async { stepRealtimeNotifier.value = -1; stepRealtimePageNotifier.value = mode; await write(BLE.BLE_Client_T_REALTIMESTEP, mode ? Uint8List.fromList([BLE_CODE_1]) : Uint8List.fromList([BLE_CODE_0])); stepRealTimeTimer?.cancel(); if (mode == true) { stepRealTimeTimer = Timer.periodic(Duration(seconds: 10), (timer) { write(BLE.BLE_Client_T_REALTIMESTEP, Uint8List.fromList([BLE_CODE_1])); }); } } Uint8List createTime(int cmd) { DateTime now = DateTime.now(); DateTime offset = DateTime(now.year, now.month, now.day, now.hour); print("create now .. $now - $offset ($offset.millisecondsSinceEpoch)"); ByteDataWriter writer = ByteDataWriter(); writer.writeUint8(cmd); writer.writeUint64(offset.millisecondsSinceEpoch); writer.writeUint8(max(0, min(60, now.minute - offset.minute))); return writer.toBytes(); } Future write(int cmd, Uint8List data) async { if (characteristicWrite == null) return; int length = data.length + 5; ByteDataWriter writer = ByteDataWriter(); writer.writeUint8(0xAA); // writer.writeUint8(0xBB); // writer.writeUint8(0xCC); writer.writeUint8(length); writer.writeUint8(0xFF - length); // writer.writeUint8(0x00); writer.writeUint8(cmd); if (data.isNotEmpty) writer.write(data); int ver = writer.toBytes().reduce((value, element) => value + element); writer.writeUint8(ver); Uint8List out = writer.toBytes(); print("out $out"); // print("out ${out.map((e) => e.toRadixString(16)).join(",")}"); for (var i = 0; i < 10; i++) { try { await flutterReactiveBle.writeCharacteristicWithoutResponse(characteristicWrite!, value: out); break; } catch (e) { print(e); await Future.delayed(Duration(milliseconds: 100)); } } } Future writeUintList(Uint8List data) async { if (characteristicWrite == null) return; int length = data.length + 4; ByteDataWriter writer = ByteDataWriter(); writer.writeUint8(0xAA); // writer.writeUint8(0xBB); // writer.writeUint8(0xCC); writer.writeUint8(length); writer.writeUint8(0xFF - length); // writer.writeUint8(0x00); if (data.isNotEmpty) writer.write(data); int ver = writer.toBytes().reduce((value, element) => value + element); writer.writeUint8(ver); Uint8List out = writer.toBytes(); print("out $out"); for (var i = 0; i < 10; i++) { try { await flutterReactiveBle.writeCharacteristicWithoutResponse(characteristicWrite!, value: out); break; } catch (e) { print(e); await Future.delayed(Duration(milliseconds: 10)); } } } _uploadLog() async { DiscoveredDevice? device = this.device; if (device == null) return; var info = infoNotifier.value; if (info.isEmpty == true) return; final api = GetIt.I(); PackageInfo packageInfo = await PackageInfo.fromPlatform(); DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); String os_name = Platform.operatingSystem; String os_ver = Platform.operatingSystemVersion; String phone_brand = ""; String phone_model = ""; if (Platform.isAndroid) { AndroidDeviceInfo androidDeviceInfo = await deviceInfo.androidInfo; phone_brand = androidDeviceInfo.brand; phone_model = androidDeviceInfo.model; } else if (Platform.isIOS) { IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo; phone_brand = iosDeviceInfo.name; phone_model = iosDeviceInfo.model; } api.onConnBt("${info['mac']}", "${info['softwareVer']}", "${info['hardwareVer']}", "${packageInfo.version}", os_name, os_ver, "${locale?.languageCode}", phone_brand, phone_model).then((value) => _uploadLoged = true); } _testErrLog() async { Directory? dir = Platform.isAndroid ? await getExternalStorageDirectory() : await getTemporaryDirectory(); if (dir != null) { var now = DateTime.now(); File file = File("${dir.path}/shoe/err_${now.year}_${now.month}_${now.day}.csv"); print("log file: $file $errQueue"); if (!file.parent.existsSync()) { file.parent.createSync(); } errQueue.forEach((element) { file.writeAsStringSync("$element\n", mode: FileMode.append, flush: true); }); } } void _testElectricity(Map info) async { Directory? dir = Platform.isAndroid ? await getExternalStorageDirectory() : await getTemporaryDirectory(); if (dir != null) { var now = DateTime.now(); File file = File("${dir.path}/shoe/electricity_${now.year}_${now.month}_${now.day}.csv"); print("log file: $file"); if (!file.parent.existsSync()) { file.parent.createSync(); } file.writeAsStringSync("${now.hour},${now.minute},${now.second},${info['0_electricity']},${info['1_electricity']},${info['0_adc']},${info['1_adc']}\n", mode: FileMode.append, flush: true); } } void _testStep(String line) async { Directory? dir = Platform.isAndroid ? await getExternalStorageDirectory() : await getTemporaryDirectory(); if (dir != null) { var now = DateTime.now(); File file = File("${dir.path}/shoe/step_${now.year}_${now.month}_${now.day}.csv"); print("log file: $file"); if (!file.parent.existsSync()) { file.parent.createSync(); } file.writeAsStringSync("$line\n", mode: FileMode.append, flush: true); } } } class PartItem { int step; int distance; int absolute; int time; PartItem(this.step, this.absolute, this.distance, this.time); Map toJson() { final Map data = new Map(); data['step'] = this.step; data['absolute'] = this.absolute; data['distance'] = this.distance; data['time'] = this.time; return data; } } class PartInfo { int start; int serial = 0; int serialCount; List items = []; late DateTime now; late DateTime maxTime; late Map> data; bool ready = false; PartInfo(this.start, this.serialCount) { now = DateTime.now(); maxTime = DateTime(now.year, now.month, now.day, now.hour); data = {}; } final int offset = 60 * 60 * 1000; prepare() async { print("bluetooth -- step prepare ... "); if (data.isEmpty) return; if (start == 0) return; var current = data[0] ?? []; if (current.isEmpty) return; int currentStep = current.first; print("bluetooth -- step current ... $currentStep"); var db = StepDB(); var history = await db.findHistory(start); print("bluetooth -- step history ... $start $history"); int startStep = 0; var p = PartItem(0, currentStep, 0, start - offset); if (history.isNotEmpty == true) { startStep = history.first['st']; if (startStep > currentStep) { await db.deleteAll(); print("bluetooth -- history ... clear"); startStep = currentStep; await db.insert(p); print("bluetooth -- step add history ... ${p.toJson()}"); } } else { startStep = currentStep; await db.insert(p); print("bluetooth -- step add history ... ${p.toJson()}"); } data[data.length] = current; for (var i = 1; i < data.length; i++) { var list = data[i] ?? []; for (int e in list) { int time = start + items.length * offset; int step = max(0, e - startStep); print("bluetooth -- step add $time $e s:$startStep step:$step"); items.add(PartItem(step, e, 0, min(time, maxTime.millisecondsSinceEpoch))); startStep = e; } } } }