import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'dart:ui'; import 'package:buffer/buffer.dart'; import 'package:flutter/material.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'package:nordic_dfu/nordic_dfu.dart'; import 'package:nrf/app_subscription_state.dart'; import 'package:nrf/scanner.dart'; import 'find.dart'; class DFUList extends StatefulWidget { final Set selectedResults; final File file; const DFUList({Key? key, required this.selectedResults, required this.file}) : super(key: key); @override State createState() => _State(); } class Device { final DiscoveredDevice device; String? msg; DiscoveredDevice? updateDevice; Device(this.device); } class DfuEvent { final String id; final String msg; DfuEvent(this.id, this.msg); } class _State extends State with SubscriptionState { late List items; String? _finishMsg; StreamSubscription? scanForDevices; final flutterReactiveBle = FlutterReactiveBle(); @override void initState() { super.initState(); items = List.from(widget.selectedResults.map((e) => Device(e))); _startDfu().then((value) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text('任务完成,总数:${widget.selectedResults.length},完成数:$value'), )); setState(() { _finishMsg = '任务完成,总数:${widget.selectedResults.length},完成数:$value'; }); // _search(); }); } @override void dispose() { scanForDevices?.cancel(); NordicDfu().abortDfu(); super.dispose(); } Future _connect(DiscoveredDevice device, Uint8List data) async { int tryCount = 3; bool _connecting = true; Timer timer = Timer(const Duration(seconds: 30), () { _connecting = false; }); await Future.delayed(const Duration(seconds: 2)); while (tryCount > 0) { if (!_connecting) return; try { final stream = flutterReactiveBle.connectToDevice(id: device.id, connectionTimeout: const Duration(seconds: 10)); await for (var state in stream) { print("device state: $state"); if (state.connectionState == DeviceConnectionState.connected) { var services = await flutterReactiveBle.discoverServices(device.id); for (DiscoveredService service in services) { for (var characteristic in service.characteristics) { if (characteristic.isWritableWithoutResponse) { QualifiedCharacteristic characteristicWrite = QualifiedCharacteristic(serviceId: service.serviceId, characteristicId: characteristic.characteristicId, deviceId: device.id); print("device characteristicWrite: ${device.id} ${service.serviceId} ${characteristic.characteristicId}"); for (var i = 0; i < 4; i++) { print("device write light"); _write(characteristicWrite, Uint8List.fromList([0xB0, 0x01])); _write(characteristicWrite, data); await Future.delayed(const Duration(milliseconds: 500)); } _connecting = false; } } } print("device disconnect"); break; } else if (state.connectionState == DeviceConnectionState.disconnected) { tryCount--; break; } } if (!_connecting) { break; } } catch (e) { print("bluetooth -- connect error: $e"); } await Future.delayed(const Duration(seconds: 3)); } timer.cancel(); } _write(QualifiedCharacteristic characteristicWrite, Uint8List data) { try { int length = data.length + 4; ByteDataWriter writer = ByteDataWriter(); writer.writeUint8(0xAA); writer.writeUint8(length); writer.writeUint8(0xFF - length); if (data.isNotEmpty) writer.write(data); int ver = writer.toBytes().reduce((value, element) => value + element); writer.writeUint8(ver); Uint8List out = writer.toBytes(); print("write ${characteristicWrite.deviceId} ${characteristicWrite.characteristicId} ${data}"); flutterReactiveBle.writeCharacteristicWithoutResponse(characteristicWrite, value: out); } catch (e) { print("write error"); print(e); } } _search() { scanForDevices?.cancel(); final flutterReactiveBle = FlutterReactiveBle(); scanForDevices = flutterReactiveBle.scanForDevices(withServices: [Uuid.parse(SH_UUID)]).listen((e) { if (e.name.isEmpty) return; final knownDeviceIndex = items.indexWhere((d) => d.device.id == e.id); if (knownDeviceIndex >= 0) { items[knownDeviceIndex].updateDevice = e; } setState(() {}); }) ..onError((e, s) { setState(() { scanForDevices = null; }); }) ..onDone(() { setState(() { scanForDevices = null; }); }); } Future _startDfu() async { String file = widget.file.path; int num = 0; for (var e in items) { if (mounted) { try { print("start dfu ${e.device.id}"); await doDfu(e.device.id, widget.file.path); print("start dfu ${e.device.id} done!"); num += 1; // ByteDataWriter writer = ByteDataWriter(); // writer.writeUint8(0xB2); // await _connect(e.device, writer.toBytes()); } catch (e) { print(e); } await Future.delayed(const Duration(seconds: 3)); } } return num; } _addLog(String deviceId, String msg) { setState(() { for (var e in items) { if (e.device.id == deviceId || deviceId.contains(e.device.id.substring(0, 12))) { e.msg = msg; } } }); } Future doDfu(String deviceId, String filePath) async { DateTime start = DateTime.now(); _addLog(deviceId, "DFU wait start!"); return await NordicDfu().startDfu( deviceId, filePath, fileInAsset: false, onEnablingDfuMode: (deviceAddress) { _addLog(deviceAddress, "EnablingDfuMode"); }, onDfuProcessStarted: (deviceAddress) { _addLog(deviceAddress, "DfuProcessStarted"); }, onDfuProcessStarting: (deviceAddress) { _addLog(deviceAddress, "DfuProcessStarting"); }, onDeviceConnecting: (deviceAddress) { _addLog(deviceAddress, "DeviceConnecting"); }, onDeviceConnected: (deviceAddress) { _addLog(deviceAddress, "DeviceConnected"); }, onDfuCompleted: (deviceAddress) { _addLog(deviceAddress, "DfuCompleted use:${DateTime.now().difference(start).inSeconds}s"); }, onFirmwareValidating: (deviceAddress) { _addLog(deviceAddress, "FirmwareValidating"); }, onProgressChanged: ( deviceAddress, percent, speed, avgSpeed, currentPart, partsTotal, ) { _addLog(deviceAddress, "$percent% speed:${avgSpeed.toStringAsFixed(2)}kb/s"); }, onError: ( String? deviceAddress, int? error, int? errorType, String? message, ) { _addLog(deviceAddress ?? "", "Error $error $errorType $message"); }, ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( actions: [ if (_finishMsg?.isNotEmpty == true) ElevatedButton( onPressed: () async { _search(); }, child: Text("检测升级后版本"), ), if (scanForDevices != null) ElevatedButton( onPressed: () async { setState(() { scanForDevices?.cancel(); scanForDevices = null; }); }, child: Text("搜索中..."), ), if (_finishMsg?.isNotEmpty != true) IconButton( icon: const CircularProgressIndicator( backgroundColor: Colors.white, ), onPressed: () {}, ), ], ), body: Column( children: [ Text("升级文件:${widget.file}"), if (_finishMsg?.isNotEmpty == true) Center( child: Text("$_finishMsg"), ), Expanded( child: ListView.builder( itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ Column( children: [ ConstrainedBox( constraints: const BoxConstraints(maxWidth: 140.0), child: Text( items[index].device.name, style: const TextStyle(fontSize: 14, color: Color(0xff333333)), softWrap: true, ), ), ConstrainedBox( constraints: const BoxConstraints(maxWidth: 140.0), child: Text( items[index].device.id, style: const TextStyle(fontSize: 12, color: Color(0xff999999)), ), ), Text( "${items[index].device.manufacturerData}", style: const TextStyle(fontSize: 12, color: Color(0xff999999)), ), if (items[index].updateDevice != null && items[index].updateDevice!.manufacturerData.length > 1) Text( " --> ${items[index].updateDevice!.manufacturerData}", style: TextStyle( fontSize: 12, color: items[index].device.manufacturerData.isEmpty ? Colors.green : (items[index].updateDevice!.manufacturerData[0] > items[index].device.manufacturerData[0]) ? Colors.green : Colors.redAccent), ), ], crossAxisAlignment: CrossAxisAlignment.start, ), const SizedBox( width: 12, ), Expanded( child: Text("${items[index].msg}"), ), ElevatedButton( onPressed: () async { await showDialog( context: context, builder: (context) => Find(device: items[index].device), ); }, child: const Text("找鞋"), ), ], ), ); }, itemCount: items.length, ), ), ], ), ); } }