import 'dart:io'; import 'dart:typed_data'; import 'package:buffer/buffer.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'package:nrf/app_subscription_state.dart'; import 'package:nrf/dfu_list.dart'; import 'package:nrf/scanner.dart'; import 'package:nrf/test.dart'; class Connector extends StatefulWidget { final DiscoveredDevice device; const Connector({Key? key, required this.device}) : super(key: key); @override State createState() => _State(); } class _State extends State with SubscriptionState { final flutterReactiveBle = FlutterReactiveBle(); ConnectionStateUpdate? _connectionStateUpdate; Object? _connectError; QualifiedCharacteristic? characteristicWrite, characteristicNotify; final Map _writeData = {}; final Map _readData = {}; late TextEditingController _textEditingController; final ValueNotifier _infoNotifier = ValueNotifier({}); final ValueNotifier _electricityNotifier = ValueNotifier({}); @override void initState() { super.initState(); _textEditingController = TextEditingController(); _connect(); } _connect() { cancel(); setState(() { _writeData.clear(); _readData.clear(); _infoNotifier.value = {}; _electricityNotifier.value = {}; characteristicWrite = characteristicNotify = null; _connectionStateUpdate = null; }); addSubscription(flutterReactiveBle .connectToDevice( id: widget.device.id, connectionTimeout: const Duration(seconds: 5), ) .listen((connectionState) { if (connectionState.connectionState == DeviceConnectionState.connected) { _discoverService(); } setState(() { _connectionStateUpdate = connectionState; }); }, onError: (Object error) { setState(() { _connectError = error; }); })); } _discoverService() { characteristicWrite = characteristicNotify = null; final deviceId = widget.device.id; flutterReactiveBle.discoverServices(widget.device.id).then((services) { for (DiscoveredService service in services) { for (var characteristic in service.characteristics) { if (characteristic.isWritableWithoutResponse) { characteristicWrite = QualifiedCharacteristic(serviceId: service.serviceId, characteristicId: characteristic.characteristicId, deviceId: deviceId); } if (characteristic.isNotifiable) { characteristicNotify = QualifiedCharacteristic(serviceId: service.serviceId, characteristicId: characteristic.characteristicId, deviceId: deviceId); } } } if (characteristicNotify != null) { addSubscription(flutterReactiveBle.subscribeToCharacteristic(characteristicNotify!).listen((event) { _parse(event); })..onError((error){setState(() { _connectError = error; });})); } setState(() {}); }).then((value) { if (characteristicWrite != null) { Stream.periodic(const Duration(seconds: 1)).take(5).forEach((element) { _write(Uint8List.fromList([0xB0, 0x01])); }); _write(Uint8List.fromList([0xA1, 0x00])); } }); } void _parse(List event) { if (!mounted) return; if (event.isEmpty == true) return; print("parse data $event"); List header = event.sublist(0, 4); List content = event.sublist(4, event.length - 1); String headerStr = header.map((e) => e.toRadixString(16).padLeft(2, "0")).join(" ").toUpperCase(); String contentStr = content.map((e) => e.toRadixString(16).padLeft(2, "0")).join(" ").toUpperCase(); setState(() { _readData[headerStr] = headerStr + " " + contentStr; }); switch (event[3]) { case 0xA1: ByteDataReader reader = ByteDataReader(); reader.add(content); int cmd = reader.readUint8(); switch (cmd) { case 0: // 设备基本信息 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.readUint16()); 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[1]}"; int compareSoftware = versionCompare(leftSoftware, rightSoftware); String minSoftwareVersion = compareSoftware < 0 ? leftSoftware : rightSoftware; print("bluetooth softwareVer $leftSoftware $rightSoftware $compareSoftware $minSoftwareVersion"); info['softwareVer'] = minSoftwareVersion; info['hardwareVer'] = minHardwareVersion; info['mac'] = device.length > 1 ? device.first : ""; info['device'] = device; _infoNotifier.value = info; break; case 1: 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(); } _electricityNotifier.value = info; break; default: break; } break; default: break; } } static int toInt(dynamic v) { if (v == null) return 0; if (v is String) { try { return int.parse(v); } catch (e) { return 0; } } else if (v is double) { try { return v.toInt(); } catch (e) { return 0; } } else if (v is int) { return v; } else if (v is num) { return v.toInt(); } return 0; } List version(String versionName) { if (versionName.isEmpty == true) return [0, 0, 0]; var arr = versionName.split("."); while (arr.length < 3) arr.add("0"); return arr.map((e) => toInt(e)).toList(); } int versionCompare(String v1, String v2) { if (v1 == v2) return 0; var clientVersion = version(v1); var baseVersion = version(v2); int client = (clientVersion[0] << 20) | (clientVersion[1] << 10) | clientVersion[2]; int base = (baseVersion[0] << 20) | (baseVersion[1] << 10) | baseVersion[2]; int result = 0; if (client > base) { result = 1; } else if (client == base) { result = 0; } else { result = -1; } print("versionCompare $clientVersion $baseVersion == $result"); return result; } Future _write(Uint8List data) async { if (!mounted) return; if (characteristicWrite == null) return; if (_connectionStateUpdate?.connectionState != DeviceConnectionState.connected) 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(); await flutterReactiveBle.writeCharacteristicWithoutResponse(characteristicWrite!, value: out); setState(() { _writeData["${data[0]}"] = out.map((e) => e.toRadixString(16).padLeft(2, "0")).join(" ").toUpperCase(); }); } @override Widget build(BuildContext context) { List readData = _readData.values.toList(); List writeData = _writeData.values.toList(); return Scaffold( appBar: AppBar(title: Text("${widget.device.name}"), actions: [ if (_connectionStateUpdate?.connectionState == DeviceConnectionState.disconnected) IconButton( onPressed: () { _connect(); }, icon: const Icon(Icons.link)) ]), body: CustomScrollView( slivers: [ SliverToBoxAdapter( child: Column( children: [ Container( padding: const EdgeInsets.all(8.0), child: Text("连接状态: $_connectionStateUpdate, $_connectError"), color: _connectionStateUpdate?.connectionState == DeviceConnectionState.connected ? Colors.green : Colors.grey, ), ValueListenableBuilder( valueListenable: _infoNotifier, builder: (_, value, __) => Padding( padding: const EdgeInsets.all(8.0), child: Text("鞋子信息: $value"), ), ), if (_connectionStateUpdate?.connectionState == DeviceConnectionState.connected) Padding( padding: const EdgeInsets.all(8.0), child: GridView( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 2, mainAxisSpacing: 4, childAspectRatio: 1.78), children: [ ElevatedButton( onPressed: () { _discoverService(); }, child: const Text("发现服务"), ), if (characteristicWrite != null) ElevatedButton( onPressed: () async { clearCache(); FilePickerResult? result = await FilePicker.platform.pickFiles(); String? path = result?.files.single.path ?? ""; if (path.isNotEmpty == true) { File file = File(path); Set selectedResults = {}; List device = _infoNotifier.value["device"]; for (var element in device) { selectedResults.add(DiscoveredDevice(id: element, name: element, manufacturerData: Uint8List.fromList([]), rssi: 0, serviceUuids: [], serviceData: {})); } Navigator.of(context).push(MaterialPageRoute(builder: (context) { return DFUList( selectedResults: selectedResults, file: file, ); })).then((value) => _connect()); } else { // User canceled the picker } }, child: const Text("DFU"), ), if (characteristicWrite != null) ElevatedButton( onPressed: () { ByteDataWriter writer = ByteDataWriter(); writer.writeUint8(0xAC); writer.writeUint16(10000); _write(writer.toBytes()); }, child: const Text("亮灯-(10s)"), ), if (characteristicWrite != null) ElevatedButton( onPressed: () { ByteDataWriter writer = ByteDataWriter(); writer.writeUint8(0xA4); writer.writeUint16(500); writer.writeUint8(1); _write(writer.toBytes()); }, child: const Text("震动(左500ms)"), ), if (characteristicWrite != null) ElevatedButton( onPressed: () { ByteDataWriter writer = ByteDataWriter(); writer.writeUint8(0xA4); writer.writeUint16(500); writer.writeUint8(2); _write(writer.toBytes()); }, child: const Text("震动(右500ms)"), ), if (characteristicWrite != null) ElevatedButton( onPressed: () { ByteDataWriter writer = ByteDataWriter(); writer.writeUint8(0xA4); writer.writeUint16(500); writer.writeUint8(0); _write(writer.toBytes()); }, child: const Text("震动(500ms)"), ), if (characteristicWrite != null) ElevatedButton( onPressed: () { _write(Uint8List.fromList([0xA2, 0x01])); }, child: const Text("游戏模式 - 开"), ), if (characteristicWrite != null) ElevatedButton( onPressed: () { _write(Uint8List.fromList([0xA2, 0x00])); }, child: const Text("游戏模式 - 关"), ), if (characteristicWrite != null) ElevatedButton( onPressed: () async { if (characteristicNotify != null) { _write(Uint8List.fromList([0xA2, 0x02])); await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); await SystemChrome.setPreferredOrientations(Platform.isIOS ? [DeviceOrientation.landscapeRight] : [DeviceOrientation.landscapeLeft]); await Navigator.of(context).push(MaterialPageRoute(builder: (context) { return Test( device: widget.device, characteristicNotify: characteristicNotify!, ); })); _write(Uint8List.fromList([0xA2, 0x00])); await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top]); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); } }, child: const Text("硬件数据"), ), if (characteristicWrite != null) ElevatedButton( onPressed: () async { while (characteristicNotify != null) { ByteDataWriter writer = ByteDataWriter(); writer.writeUint8(0xAC); writer.writeUint16(10000); _write(writer.toBytes()); await Future.delayed(const Duration(seconds: 1)); writer = ByteDataWriter(); writer.writeUint8(0xA4); writer.writeUint16(500); writer.writeUint8(0); _write(writer.toBytes()); await Future.delayed(const Duration(seconds: 1)); } }, child: const Text("放电"), ), if (characteristicWrite != null) ElevatedButton( onPressed: () async { _write(Uint8List.fromList([0xA1, 0x01])); }, child: const Text("电量"), ), ElevatedButton( onPressed: () async { while (mounted) { cancel(); await Future.delayed(const Duration(seconds: 3)); _connect(); await Future.delayed(const Duration(seconds: 5)); } }, child: const Text("测试10秒重新连接循化"), ),ElevatedButton( onPressed: () async { cancel(); await Future.delayed(const Duration(seconds: 3)); _connect(); }, child: const Text("重新连接"), ), ], ), ), if (characteristicWrite != null) Container( padding: const EdgeInsets.all(12.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Expanded( child: CupertinoTextField( cursorColor: const Color(0xffFFC400), controller: _textEditingController, )), ElevatedButton( onPressed: () { var data = _textEditingController.value.text; if (data.isEmpty) return; List list = data.split(" ").where((element) => element.isNotEmpty).map((e) => int.parse(e, radix: 16)).toList(); _write(Uint8List.fromList(list)); }, child: const Text("发送"), ), ], ), ValueListenableBuilder( valueListenable: _electricityNotifier, builder: (BuildContext context, value, Widget? child) => Text( "电量: $value", ), ), ], ), ), ], ), ), if (characteristicWrite != null) const SliverToBoxAdapter( child: Padding( padding: EdgeInsets.all(12.0), child: Text("发送历史"), ), ), if (characteristicWrite != null) SliverList( delegate: SliverChildBuilderDelegate((content, index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Text("--> ${writeData[index]}"), ); }, childCount: writeData.length), ), if (characteristicNotify != null) const SliverToBoxAdapter( child: Padding( padding: EdgeInsets.all(12.0), child: Text("接收历史"), ), ), if (characteristicWrite != null) SliverList( delegate: SliverChildBuilderDelegate((content, index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Text("<-- ${readData[index]}"), ); }, childCount: readData.length), ), ], ), ); } }