dfu_list.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'dart:typed_data';
  4. import 'dart:ui';
  5. import 'package:buffer/buffer.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
  8. import 'package:nordic_dfu/nordic_dfu.dart';
  9. import 'package:nrf/app_subscription_state.dart';
  10. import 'package:nrf/scanner.dart';
  11. import 'find.dart';
  12. class DFUList extends StatefulWidget {
  13. final Set<DiscoveredDevice> selectedResults;
  14. final File file;
  15. const DFUList({Key? key, required this.selectedResults, required this.file}) : super(key: key);
  16. @override
  17. State<StatefulWidget> createState() => _State();
  18. }
  19. class Device {
  20. final DiscoveredDevice device;
  21. String? msg;
  22. DiscoveredDevice? updateDevice;
  23. Device(this.device);
  24. }
  25. class DfuEvent {
  26. final String id;
  27. final String msg;
  28. DfuEvent(this.id, this.msg);
  29. }
  30. class _State extends State<DFUList> with SubscriptionState {
  31. late List<Device> items;
  32. String? _finishMsg;
  33. StreamSubscription? scanForDevices;
  34. final flutterReactiveBle = FlutterReactiveBle();
  35. @override
  36. void initState() {
  37. super.initState();
  38. items = List.from(widget.selectedResults.map((e) => Device(e)));
  39. _startDfu().then((value) {
  40. ScaffoldMessenger.of(context).showSnackBar(SnackBar(
  41. content: Text('任务完成,总数:${widget.selectedResults.length},完成数:$value'),
  42. ));
  43. setState(() {
  44. _finishMsg = '任务完成,总数:${widget.selectedResults.length},完成数:$value';
  45. });
  46. // _search();
  47. });
  48. }
  49. @override
  50. void dispose() {
  51. scanForDevices?.cancel();
  52. NordicDfu().abortDfu();
  53. super.dispose();
  54. }
  55. Future _connect(DiscoveredDevice device, Uint8List data) async {
  56. int tryCount = 3;
  57. bool _connecting = true;
  58. Timer timer = Timer(const Duration(seconds: 30), () {
  59. _connecting = false;
  60. });
  61. await Future.delayed(const Duration(seconds: 2));
  62. while (tryCount > 0) {
  63. if (!_connecting) return;
  64. try {
  65. final stream = flutterReactiveBle.connectToDevice(id: device.id, connectionTimeout: const Duration(seconds: 10));
  66. await for (var state in stream) {
  67. print("device state: $state");
  68. if (state.connectionState == DeviceConnectionState.connected) {
  69. var services = await flutterReactiveBle.discoverServices(device.id);
  70. for (DiscoveredService service in services) {
  71. for (var characteristic in service.characteristics) {
  72. if (characteristic.isWritableWithoutResponse) {
  73. QualifiedCharacteristic characteristicWrite = QualifiedCharacteristic(serviceId: service.serviceId, characteristicId: characteristic.characteristicId, deviceId: device.id);
  74. print("device characteristicWrite: ${device.id} ${service.serviceId} ${characteristic.characteristicId}");
  75. for (var i = 0; i < 4; i++) {
  76. print("device write light");
  77. _write(characteristicWrite, Uint8List.fromList([0xB0, 0x01]));
  78. _write(characteristicWrite, data);
  79. await Future.delayed(const Duration(milliseconds: 500));
  80. }
  81. _connecting = false;
  82. }
  83. }
  84. }
  85. print("device disconnect");
  86. break;
  87. } else if (state.connectionState == DeviceConnectionState.disconnected) {
  88. tryCount--;
  89. break;
  90. }
  91. }
  92. if (!_connecting) {
  93. break;
  94. }
  95. } catch (e) {
  96. print("bluetooth -- connect error: $e");
  97. }
  98. await Future.delayed(const Duration(seconds: 3));
  99. }
  100. timer.cancel();
  101. }
  102. _write(QualifiedCharacteristic characteristicWrite, Uint8List data) {
  103. try {
  104. int length = data.length + 4;
  105. ByteDataWriter writer = ByteDataWriter();
  106. writer.writeUint8(0xAA);
  107. writer.writeUint8(length);
  108. writer.writeUint8(0xFF - length);
  109. if (data.isNotEmpty) writer.write(data);
  110. int ver = writer.toBytes().reduce((value, element) => value + element);
  111. writer.writeUint8(ver);
  112. Uint8List out = writer.toBytes();
  113. print("write ${characteristicWrite.deviceId} ${characteristicWrite.characteristicId} ${data}");
  114. flutterReactiveBle.writeCharacteristicWithoutResponse(characteristicWrite, value: out);
  115. } catch (e) {
  116. print("write error");
  117. print(e);
  118. }
  119. }
  120. _search() {
  121. scanForDevices?.cancel();
  122. final flutterReactiveBle = FlutterReactiveBle();
  123. scanForDevices = flutterReactiveBle.scanForDevices(withServices: [Uuid.parse(SH_UUID)]).listen((e) {
  124. if (e.name.isEmpty) return;
  125. final knownDeviceIndex = items.indexWhere((d) => d.device.id == e.id);
  126. if (knownDeviceIndex >= 0) {
  127. items[knownDeviceIndex].updateDevice = e;
  128. }
  129. setState(() {});
  130. })
  131. ..onError((e, s) {
  132. setState(() {
  133. scanForDevices = null;
  134. });
  135. })
  136. ..onDone(() {
  137. setState(() {
  138. scanForDevices = null;
  139. });
  140. });
  141. }
  142. Future<int> _startDfu() async {
  143. String file = widget.file.path;
  144. int num = 0;
  145. for (var e in items) {
  146. if (mounted) {
  147. try {
  148. print("start dfu ${e.device.id}");
  149. await doDfu(e.device.id, widget.file.path);
  150. print("start dfu ${e.device.id} done!");
  151. num += 1;
  152. // ByteDataWriter writer = ByteDataWriter();
  153. // writer.writeUint8(0xB2);
  154. // await _connect(e.device, writer.toBytes());
  155. } catch (e) {
  156. print(e);
  157. }
  158. await Future.delayed(const Duration(seconds: 3));
  159. }
  160. }
  161. return num;
  162. }
  163. _addLog(String deviceId, String msg) {
  164. setState(() {
  165. for (var e in items) {
  166. if (e.device.id == deviceId || deviceId.contains(e.device.id.substring(0, 12))) {
  167. e.msg = msg;
  168. }
  169. }
  170. });
  171. }
  172. Future<String?> doDfu(String deviceId, String filePath) async {
  173. DateTime start = DateTime.now();
  174. _addLog(deviceId, "DFU wait start!");
  175. return await NordicDfu().startDfu(
  176. deviceId,
  177. filePath,
  178. fileInAsset: false,
  179. onEnablingDfuMode: (deviceAddress) {
  180. _addLog(deviceAddress, "EnablingDfuMode");
  181. },
  182. onDfuProcessStarted: (deviceAddress) {
  183. _addLog(deviceAddress, "DfuProcessStarted");
  184. },
  185. onDfuProcessStarting: (deviceAddress) {
  186. _addLog(deviceAddress, "DfuProcessStarting");
  187. },
  188. onDeviceConnecting: (deviceAddress) {
  189. _addLog(deviceAddress, "DeviceConnecting");
  190. },
  191. onDeviceConnected: (deviceAddress) {
  192. _addLog(deviceAddress, "DeviceConnected");
  193. },
  194. onDfuCompleted: (deviceAddress) {
  195. _addLog(deviceAddress, "DfuCompleted use:${DateTime.now().difference(start).inSeconds}s");
  196. },
  197. onFirmwareValidating: (deviceAddress) {
  198. _addLog(deviceAddress, "FirmwareValidating");
  199. },
  200. onProgressChanged: (
  201. deviceAddress,
  202. percent,
  203. speed,
  204. avgSpeed,
  205. currentPart,
  206. partsTotal,
  207. ) {
  208. _addLog(deviceAddress, "$percent% speed:${avgSpeed.toStringAsFixed(2)}kb/s");
  209. },
  210. onError: (
  211. String? deviceAddress,
  212. int? error,
  213. int? errorType,
  214. String? message,
  215. ) {
  216. _addLog(deviceAddress ?? "", "Error $error $errorType $message");
  217. },
  218. );
  219. }
  220. @override
  221. Widget build(BuildContext context) {
  222. return Scaffold(
  223. appBar: AppBar(
  224. actions: [
  225. if (_finishMsg?.isNotEmpty == true)
  226. ElevatedButton(
  227. onPressed: () async {
  228. _search();
  229. },
  230. child: Text("检测升级后版本"),
  231. ),
  232. if (scanForDevices != null)
  233. ElevatedButton(
  234. onPressed: () async {
  235. setState(() {
  236. scanForDevices?.cancel();
  237. scanForDevices = null;
  238. });
  239. },
  240. child: Text("搜索中..."),
  241. ),
  242. if (_finishMsg?.isNotEmpty != true)
  243. IconButton(
  244. icon: const CircularProgressIndicator(
  245. backgroundColor: Colors.white,
  246. ),
  247. onPressed: () {},
  248. ),
  249. ],
  250. ),
  251. body: Column(
  252. children: [
  253. Text("升级文件:${widget.file}"),
  254. if (_finishMsg?.isNotEmpty == true)
  255. Center(
  256. child: Text("$_finishMsg"),
  257. ),
  258. Expanded(
  259. child: ListView.builder(
  260. itemBuilder: (context, index) {
  261. return Padding(
  262. padding: const EdgeInsets.all(8.0),
  263. child: Row(
  264. children: [
  265. Column(
  266. children: [
  267. ConstrainedBox(
  268. constraints: const BoxConstraints(maxWidth: 140.0),
  269. child: Text(
  270. items[index].device.name,
  271. style: const TextStyle(fontSize: 14, color: Color(0xff333333)),
  272. softWrap: true,
  273. ),
  274. ),
  275. ConstrainedBox(
  276. constraints: const BoxConstraints(maxWidth: 140.0),
  277. child: Text(
  278. items[index].device.id,
  279. style: const TextStyle(fontSize: 12, color: Color(0xff999999)),
  280. ),
  281. ),
  282. Text(
  283. "${items[index].device.manufacturerData}",
  284. style: const TextStyle(fontSize: 12, color: Color(0xff999999)),
  285. ),
  286. if (items[index].updateDevice != null && items[index].updateDevice!.manufacturerData.length > 1)
  287. Text(
  288. " --> ${items[index].updateDevice!.manufacturerData}",
  289. style: TextStyle(
  290. fontSize: 12,
  291. color: items[index].device.manufacturerData.isEmpty
  292. ? Colors.green
  293. : (items[index].updateDevice!.manufacturerData[0] > items[index].device.manufacturerData[0])
  294. ? Colors.green
  295. : Colors.redAccent),
  296. ),
  297. ],
  298. crossAxisAlignment: CrossAxisAlignment.start,
  299. ),
  300. const SizedBox(
  301. width: 12,
  302. ),
  303. Expanded(
  304. child: Text("${items[index].msg}"),
  305. ),
  306. ElevatedButton(
  307. onPressed: () async {
  308. await showDialog(
  309. context: context,
  310. builder: (context) => Find(device: items[index].device),
  311. );
  312. },
  313. child: const Text("找鞋"),
  314. ),
  315. ],
  316. ),
  317. );
  318. },
  319. itemCount: items.length,
  320. ),
  321. ),
  322. ],
  323. ),
  324. );
  325. }
  326. }