kidd3166 2 years ago
parent
commit
6efe1b0473

+ 3 - 1
android/app/build.gradle

@@ -53,7 +53,9 @@ android {
 
     buildTypes {
         release {
-            // TODO: Add your own signing config for the release build.
+            minifyEnabled false
+            zipAlignEnabled true
+            shrinkResources false
             // Signing with the debug keys for now, so `flutter run --release` works.
             signingConfig signingConfigs.debug
         }

+ 3 - 0
lib/connector.dart

@@ -9,6 +9,7 @@ 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 {
@@ -278,6 +279,8 @@ class _State extends State<Connector> with SubscriptionState {
                         if (characteristicWrite != null)
                           ElevatedButton(
                             onPressed: () async {
+                              clearCache();
+
                               FilePickerResult? result = await FilePicker.platform.pickFiles();
 
                               String? path = result?.files.single.path ?? "";

+ 7 - 19
lib/dfu_list.dart

@@ -1,6 +1,5 @@
 import 'dart:async';
 import 'dart:io';
-import 'dart:math';
 import 'dart:typed_data';
 import 'dart:ui';
 
@@ -10,7 +9,6 @@ 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 'package:path_provider/path_provider.dart';
 
 import 'find.dart';
 
@@ -60,7 +58,7 @@ class _State extends State<DFUList> with SubscriptionState {
       setState(() {
         _finishMsg = '任务完成,总数:${widget.selectedResults.length},完成数:$value';
       });
-      _search();
+      // _search();
     });
   }
 
@@ -96,7 +94,7 @@ class _State extends State<DFUList> with SubscriptionState {
                   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++){
+                  for (var i = 0; i < 4; i++) {
                     print("device write light");
                     _write(characteristicWrite, Uint8List.fromList([0xB0, 0x01]));
                     _write(characteristicWrite, data);
@@ -127,7 +125,7 @@ class _State extends State<DFUList> with SubscriptionState {
     timer.cancel();
   }
 
-  _write(QualifiedCharacteristic characteristicWrite, Uint8List data) async{
+  _write(QualifiedCharacteristic characteristicWrite, Uint8List data) {
     try {
       int length = data.length + 4;
       ByteDataWriter writer = ByteDataWriter();
@@ -139,7 +137,7 @@ class _State extends State<DFUList> with SubscriptionState {
       writer.writeUint8(ver);
       Uint8List out = writer.toBytes();
       print("write ${characteristicWrite.deviceId} ${characteristicWrite.characteristicId} ${data}");
-      await flutterReactiveBle.writeCharacteristicWithoutResponse(characteristicWrite, value: out);
+      flutterReactiveBle.writeCharacteristicWithoutResponse(characteristicWrite, value: out);
     } catch (e) {
       print("write error");
       print(e);
@@ -171,14 +169,7 @@ class _State extends State<DFUList> with SubscriptionState {
   }
 
   Future<int> _startDfu() async {
-
     String file = widget.file.path;
-    if (Platform.isIOS) {
-      var dir = await getApplicationDocumentsDirectory();
-      file = "${dir.path}/${DateTime.now().millisecondsSinceEpoch}.zip";
-      await File(widget.file.path).copy(file);
-    }
-
     int num = 0;
     for (var e in items) {
       if (mounted) {
@@ -188,18 +179,15 @@ class _State extends State<DFUList> with SubscriptionState {
           print("start dfu ${e.device.id} done!");
           num += 1;
 
-          ByteDataWriter writer = ByteDataWriter();
-          writer.writeUint8(0xB2);
-          await _connect(e.device, writer.toBytes());
+          // ByteDataWriter writer = ByteDataWriter();
+          // writer.writeUint8(0xB2);
+          // await _connect(e.device, writer.toBytes());
         } catch (e) {
           print(e);
         }
         await Future.delayed(const Duration(seconds: 3));
       }
     }
-    if (Platform.isIOS) {
-      File(file).deleteSync();
-    }
     return num;
   }
 

+ 616 - 301
lib/scanner.dart

@@ -1,17 +1,46 @@
 import 'dart:async';
 import 'dart:io';
+import 'dart:math';
 
 import 'package:file_picker/file_picker.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/connector.dart';
 import 'package:nrf/dfu_list.dart';
 import 'package:nrf/find.dart';
+import 'package:path_provider/path_provider.dart';
 import 'package:permission_handler/permission_handler.dart';
+import 'package:wakelock/wakelock.dart';
 
 const SH_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
 
+Future clearCache() async {
+  await delDir(await getTemporaryDirectory());
+  if (Platform.isAndroid) {
+    var dir = await getExternalStorageDirectory();
+    if (dir != null) await delDir(dir);
+  }
+}
+
+///递归方式删除目录
+Future<Null> delDir(FileSystemEntity file) async {
+  print(("del path $file"));
+  if (file is Directory) {
+    try {
+      final List<FileSystemEntity> children = file.listSync();
+      for (final FileSystemEntity child in children) {
+        await delDir(child);
+      }
+    } catch (e) {
+      print(e);
+    }
+  } else {
+    await file.delete();
+  }
+}
+
 class Scanner extends StatefulWidget {
   const Scanner({Key? key}) : super(key: key);
 
@@ -23,7 +52,7 @@ class _State extends State<Scanner> with SubscriptionState {
   final flutterReactiveBle = FlutterReactiveBle();
 
   bool _selectAll = false;
-  double _filterRssi = 70;
+  double _filterRssi = 35;
   bool _filterNameEquals = true;
   bool _filterSofewareEquals = true;
   bool _filterHardwareEquals = true;
@@ -35,6 +64,13 @@ class _State extends State<Scanner> with SubscriptionState {
   List<DiscoveredDevice> scanResults = <DiscoveredDevice>[];
   Set<DiscoveredDevice> selectedResults = {};
 
+  bool _dfuAll = false;
+  DiscoveredDevice? _targetDevice;
+  int _countAll = 0;
+  int _countSucc = 0;
+  int _countFail = 0;
+  final ValueNotifier<String> _messageNotifier = ValueNotifier<String>("");
+
   @override
   void initState() {
     super.initState();
@@ -47,6 +83,8 @@ class _State extends State<Scanner> with SubscriptionState {
     }));
 
     _grant();
+    clearCache();
+    Wakelock.enable();
   }
 
   _grant() async {
@@ -73,6 +111,7 @@ class _State extends State<Scanner> with SubscriptionState {
 
     scanForDevices = flutterReactiveBle.scanForDevices(withServices: [Uuid.parse(SH_UUID)]).listen((e) {
       if (e.name.isEmpty) return;
+      // print("111111111111111111111111 ${e}");
       final knownDeviceIndex = scanResults.indexWhere((d) => d.id == e.id);
       if (knownDeviceIndex >= 0) {
         scanResults[knownDeviceIndex] = e;
@@ -88,6 +127,153 @@ class _State extends State<Scanner> with SubscriptionState {
       });
   }
 
+  List<DiscoveredDevice> updateResults = <DiscoveredDevice>[];
+  Map<DiscoveredDevice, int> failedResults = {};
+
+  _dfu(String path, int type) async {
+    updateResults.clear();
+    failedResults.clear();
+
+    while (mounted) {
+      try {
+        _messageNotifier.value = "开始搜索...(M$type)";
+        DiscoveredDevice device;
+        if (type == 1) {
+          List<DiscoveredDevice> scanResults = <DiscoveredDevice>[];
+          Completer<bool> completer = Completer();
+          int times = 0;
+          Timer timeout = Timer.periodic(const Duration(seconds: 5), (t) {
+            if(scanResults.isNotEmpty) {
+              completer.complete(true);
+            }else{
+              _messageNotifier.value = "开始搜索...(M$type .... ${times++})";
+            }
+          });
+
+          var stream = flutterReactiveBle.scanForDevices(withServices: [Uuid.parse(SH_UUID)], scanMode: ScanMode.lowPower);
+          var listen = stream.listen((event) {
+            if (scanResults.where((element) => element.id == event.id).isEmpty && (failedResults[event] ?? 0) < 3) {
+              List<DiscoveredDevice> results = [event];
+              scanResults.addAll(results
+                  .where((element) => updateResults.where((event) => element.id == event.id).isEmpty)
+                  .where((element) => element.rssi.abs() < _filterRssi).where((element) => element.name.contains(_filterName ?? "") == _filterNameEquals).where((element) {
+                if (_filterHardware?.isNotEmpty == true) {
+                  if (element.manufacturerData.length >= 6 && (_filterHardware == "${element.manufacturerData[3]}.${element.manufacturerData[4]}.${element.manufacturerData[5]}") == _filterHardwareEquals) {
+                    return true;
+                  } else {
+                    return false;
+                  }
+                }
+                return true;
+              }).where((element) {
+                if (_filterSofeware?.isNotEmpty == true) {
+                  if (element.manufacturerData.length >= 6 && (_filterSofeware == "${element.manufacturerData[3]}.${element.manufacturerData[4]}.${element.manufacturerData[0]}") == _filterSofewareEquals) {
+                    return true;
+                  } else {
+                    return false;
+                  }
+                }
+                return true;
+              }));
+            }
+          });
+          await completer.future;
+          listen.cancel();
+          timeout.cancel();
+
+          _messageNotifier.value = "搜索到 ${scanResults.map((e) => e.name).join(", ")}";
+
+          if (scanResults.isEmpty) {
+            await Future.delayed(const Duration(seconds: 2));
+            continue;
+          }
+
+          device = scanResults[Random().nextInt(scanResults.length)];
+        } else {
+          device = await flutterReactiveBle
+              .scanForDevices(withServices: [Uuid.parse(SH_UUID)])
+              .where((element) => updateResults.where((event) => element.id == event.id).isEmpty)
+              .where((element) => failedResults.keys.where((event) => element.id == event.id).isEmpty)
+              .where((element) => element.rssi.abs() < _filterRssi)
+              .where((element) => element.name.contains(_filterName ?? "") == _filterNameEquals)
+              .where((element) {
+                if (_filterHardware?.isNotEmpty == true) {
+                  if (element.manufacturerData.length >= 6 && (_filterHardware == "${element.manufacturerData[3]}.${element.manufacturerData[4]}.${element.manufacturerData[5]}") == _filterHardwareEquals) {
+                    return true;
+                  } else {
+                    return false;
+                  }
+                }
+                return true;
+              })
+              .firstWhere((element) {
+                if (_filterSofeware?.isNotEmpty == true) {
+                  if (element.manufacturerData.length >= 6 && (_filterSofeware == "${element.manufacturerData[3]}.${element.manufacturerData[4]}.${element.manufacturerData[0]}") == _filterSofewareEquals) {
+                    return true;
+                  } else {
+                    return false;
+                  }
+                }
+                return true;
+              });
+        }
+
+        setState(() {
+          _countAll++;
+          _targetDevice = device;
+        });
+        await Future.delayed(const Duration(seconds: 2));
+
+        DateTime start = DateTime.now();
+        try {
+          await NordicDfu().startDfu(device.id, path, fileInAsset: false, onEnablingDfuMode: (deviceAddress) {
+            _messageNotifier.value = "EnablingDfuMode";
+          }, onDfuCompleted: (deviceAddress) {
+            _messageNotifier.value = "DfuCompleted use:${DateTime.now().difference(start).inSeconds}s";
+            _countSucc++;
+            updateResults.add(device);
+          }, onDfuProcessStarted: (deviceAddress) {
+            _messageNotifier.value = "DfuProcessStarted";
+          }, onDfuProcessStarting: (deviceAddress) {
+            _messageNotifier.value = "DfuProcessStarting";
+          }, onDeviceConnecting: (deviceAddress) {
+            _messageNotifier.value = "DeviceConnecting";
+          }, onDeviceConnected: (deviceAddress) {
+            _messageNotifier.value = "DeviceConnected";
+          }, onProgressChanged: (
+            deviceAddress,
+            percent,
+            speed,
+            avgSpeed,
+            currentPart,
+            partsTotal,
+          ) {
+            _messageNotifier.value = "$percent% speed:${avgSpeed.toStringAsFixed(2)}kb/s";
+          }, onError: (
+            String? deviceAddress,
+            int? error,
+            int? errorType,
+            String? message,
+          ) {
+            _messageNotifier.value = "Error $error $errorType $message";
+            _countFail++;
+            failedResults[device] = (failedResults[device] ?? 0)+1;
+          });
+        } catch (e) {
+          print(e);
+          _messageNotifier.value = "升级异常: $e";
+        }
+      } catch (e) {
+        print(e);
+        _messageNotifier.value = "异常: $e";
+      }
+      setState(() {
+        _targetDevice = null;
+      });
+      await Future.delayed(const Duration(seconds: 2));
+    }
+  }
+
   @override
   Widget build(BuildContext context) {
     List<DiscoveredDevice> items = <DiscoveredDevice>[];
@@ -114,155 +300,137 @@ class _State extends State<Scanner> with SubscriptionState {
       return a.rssi.abs() > b.rssi.abs() ? 1 : -1;
     });
     return Scaffold(
-      appBar: AppBar(
-        title: const Text("发现设备"),
-        actions: [
-          Padding(
-            padding: const EdgeInsets.all(8.0),
-            child: ElevatedButton(
-                onPressed: () {
-                  setState(() {
-                    scanForDevices?.cancel();
-                    scanForDevices = null;
-                    _selectAll = !_selectAll;
-                  });
-                },
-                child: _selectAll ? const Text("取消多选") : const Text("多选")),
-          ),
-          scanForDevices == null
-              ? IconButton(
-                  onPressed: () {
-                    setState(() {
-                      _search();
-                    });
-                  },
-                  icon: const Icon(Icons.search))
-              : IconButton(
-                  onPressed: () {
-                    setState(() {
-                      scanForDevices?.cancel();
-                      scanForDevices = null;
-                    });
-                  },
-                  icon: const Icon(Icons.stop))
-        ],
-      ),
-      body: Column(
-        children: [
-          Container(
-            padding: const EdgeInsets.all(12.0),
-            child: Column(
-              children: [
-                Row(
-                  children: [
-                    const Text("rssi"),
-                    Padding(
-                      padding: const EdgeInsets.symmetric(horizontal: 8.0),
-                      child: Text("< ${_filterRssi.toInt()}"),
-                    ),
-                    Expanded(
-                      child: Slider(
-                        min: 10,
-                        max: 100,
-                        value: _filterRssi,
-                        onChanged: (v) {
-                          setState(() {
-                            _filterRssi = v;
-                          });
-                        },
-                      ),
-                    )
-                  ],
-                ),
-                Row(
-                  children: [
-                    const Text("关键字"),
-                    Checkbox(
-                      value: _filterNameEquals,
-                      onChanged: (bool? value) {
-                        setState(() {
-                          _filterNameEquals = value ?? false;
-                        });
-                      },
-                    ),
-                    Expanded(
-                      child: Padding(
-                        padding: const EdgeInsets.symmetric(horizontal: 10.0),
-                        child: TextField(
-                          controller: TextEditingController.fromValue(TextEditingValue(
-                              text: _filterName ?? "",
-                              selection: TextSelection.fromPosition(TextPosition(
-                                affinity: TextAffinity.downstream,
-                                offset: _filterName == null ? 0 : _filterName?.length ?? 0,
-                              )))),
-                          onChanged: (v) {
-                            setState(() {
-                              _filterName = v;
-                            });
-                          },
-                        ),
-                      ),
-                    ),
-                    ElevatedButton(
+      appBar: _dfuAll
+          ? null
+          : AppBar(
+              title: const Text("发现设备"),
+              actions: [
+                Padding(
+                  padding: const EdgeInsets.all(8.0),
+                  child: ElevatedButton(
                       onPressed: () {
                         setState(() {
-                          _filterName = "FUN_";
+                          scanForDevices?.cancel();
+                          scanForDevices = null;
+                          _selectAll = !_selectAll;
                         });
                       },
-                      child: const Text("FUN"),
+                      child: _selectAll ? const Text("取消多选") : const Text("多选")),
+                ),
+                scanForDevices == null
+                    ? IconButton(
+                        onPressed: () {
+                          setState(() {
+                            _search();
+                          });
+                        },
+                        icon: const Icon(Icons.search))
+                    : IconButton(
+                        onPressed: () {
+                          setState(() {
+                            scanForDevices?.cancel();
+                            scanForDevices = null;
+                          });
+                        },
+                        icon: const Icon(Icons.stop))
+              ],
+            ),
+      body: _dfuAll
+          ? Container(
+              width: double.infinity,
+              height: double.infinity,
+              color: Colors.white,
+              child: Column(
+                children: [
+                  const SizedBox(
+                    height: 50,
+                  ),
+                  Text("升级固件(总数:$_countAll/成功:$_countSucc/失败:$_countFail)"),
+                  const SizedBox(
+                    height: 25,
+                  ),
+                  ConstrainedBox(
+                    constraints: const BoxConstraints(maxWidth: 140.0),
+                    child: Text(
+                      "${_targetDevice?.name}",
+                      style: const TextStyle(fontSize: 14, color: Color(0xff333333)),
+                      softWrap: true,
                     ),
-                    const SizedBox(
-                      width: 10,
+                  ),
+                  const SizedBox(
+                    height: 25,
+                  ),
+                  ConstrainedBox(
+                    constraints: const BoxConstraints(maxWidth: 140.0),
+                    child: Text(
+                      "${_targetDevice?.id}",
+                      style: const TextStyle(fontSize: 12, color: Color(0xff999999)),
                     ),
-                    ElevatedButton(
-                      onPressed: () {
-                        setState(() {
-                          _filterName = "";
-                        });
-                      },
-                      child: const Text("CLEAR"),
+                  ),
+                  const SizedBox(
+                    height: 25,
+                  ),
+                  Text(
+                    "${_targetDevice?..manufacturerData}",
+                    style: const TextStyle(fontSize: 12, color: Color(0xff999999)),
+                  ),
+                  const SizedBox(
+                    height: 25,
+                  ),
+                  ValueListenableBuilder(
+                    valueListenable: _messageNotifier,
+                    builder: (BuildContext context, String value, Widget? child) => Text(
+                      value,
                     ),
-                  ],
-                ),
-                Row(
-                  children: [
-                    Expanded(
-                      child: Row(
+                  ),
+                  const SizedBox(
+                    height: 50,
+                  ),
+                  Container(
+                    padding: const EdgeInsets.all(12.0),
+                    height: 200,
+                    child: SingleChildScrollView(
+                      child: Text("升级列表 ... ${updateResults.map((e) => e.name).join(", ")}"),
+                    ),
+                  )
+                ],
+              ),
+            )
+          : Column(
+              children: [
+                Container(
+                  padding: const EdgeInsets.all(12.0),
+                  child: Column(
+                    children: [
+                      Row(
                         children: [
-                          const Text("固件"),
-                          Checkbox(
-                            value: _filterSofewareEquals,
-                            onChanged: (bool? value) {
-                              setState(() {
-                                _filterSofewareEquals = value ?? false;
-                              });
-                            },
+                          const Text("rssi"),
+                          Padding(
+                            padding: const EdgeInsets.symmetric(horizontal: 8.0),
+                            child: Text("< ${_filterRssi.toInt()}"),
                           ),
                           Expanded(
-                            child: Padding(
-                              padding: const EdgeInsets.symmetric(horizontal: 10.0),
-                              child: TextField(
-                                keyboardType: const TextInputType.numberWithOptions(decimal: true),
-                                onChanged: (v) {
-                                  setState(() {
-                                    _filterSofeware = v;
-                                  });
-                                },
-                              ),
+                            child: Slider(
+                              min: 20,
+                              max: 100,
+                              value: _filterRssi,
+                              onChanged: (v) {
+                                setState(() {
+                                  _filterRssi = v;
+                                });
+                              },
                             ),
-                          ),
+                          )
                         ],
                       ),
-                    ),
-                    Expanded(
-                      child: Row(
+                      Row(
                         children: [
-                          const Text("硬件"),
+                          const Text("关键字"),
                           Checkbox(
-                            value: _filterHardwareEquals,
+                            value: _filterNameEquals,
                             onChanged: (bool? value) {
                               setState(() {
-                                _filterHardwareEquals = value ?? false;
+                                _filterNameEquals = value ?? false;
                               });
                             },
                           ),
@@ -270,200 +438,347 @@ class _State extends State<Scanner> with SubscriptionState {
                             child: Padding(
                               padding: const EdgeInsets.symmetric(horizontal: 10.0),
                               child: TextField(
-                                keyboardType: const TextInputType.numberWithOptions(decimal: true),
+                                controller: TextEditingController.fromValue(TextEditingValue(
+                                    text: _filterName ?? "",
+                                    selection: TextSelection.fromPosition(TextPosition(
+                                      affinity: TextAffinity.downstream,
+                                      offset: _filterName == null ? 0 : _filterName?.length ?? 0,
+                                    )))),
                                 onChanged: (v) {
                                   setState(() {
-                                    _filterHardware = v;
+                                    _filterName = v;
                                   });
                                 },
                               ),
                             ),
                           ),
-                        ],
-                      ),
-                    ),
-                  ],
-                ),
-              ],
-            ),
-          ),
-          if (scanForDevices == null && items.isEmpty)
-            Padding(
-              padding: const EdgeInsets.all(40.0),
-              child: ElevatedButton(
-                onPressed: () {
-                  _search();
-                },
-                child: Text("搜索设备"),
-              ),
-            ),
-          Expanded(
-              child: ListView.builder(
-            itemBuilder: (context, index) {
-              var e = items[index];
-              return GestureDetector(
-                child: Card(
-                  child: Padding(
-                    padding: const EdgeInsets.all(12.0),
-                    child: Row(
-                      children: [
-                        Expanded(
-                          child: Column(
-                            children: [
-                              Text(
-                                e.name,
-                                style: Theme.of(context).textTheme.headline6,
-                              ),
-                              ConstrainedBox(constraints: const BoxConstraints(maxWidth: 140.0), child: Text(e.id)),
-                              Text("${e.manufacturerData}"),
-                              Row(
-                                children: [
-                                  Text("${e.rssi} dBm"),
-                                  const SizedBox(
-                                    width: 20,
-                                  ),
-                                  Text("#${index + 1}"),
-                                ],
-                              ),
-                            ],
-                            crossAxisAlignment: CrossAxisAlignment.start,
-                          ),
-                        ),
-                        Padding(
-                          padding: const EdgeInsets.symmetric(horizontal: 8.0),
-                          child: ElevatedButton(
-                            onPressed: () async {
-                              await showDialog(
-                                context: context,
-                                builder: (context) => Find(device: e),
-                              );
-                            },
-                            child: const Text("找鞋"),
-                          ),
-                        ),
-                        if (!_selectAll)
                           ElevatedButton(
                             onPressed: () {
                               setState(() {
-                                scanForDevices?.cancel();
-                                scanForDevices = null;
+                                _filterName = "FUN_";
                               });
-
-                              Navigator.of(context).push(MaterialPageRoute(builder: (context) {
-                                return Connector(
-                                  device: e,
-                                );
-                              }));
                             },
-                            child: const Text("CONNECT"),
+                            child: const Text("FUN"),
                           ),
-                        if (_selectAll)
-                          Checkbox(
-                            value: selectedResults.contains(e),
-                            onChanged: (bool? value) {
+                          const SizedBox(
+                            width: 10,
+                          ),
+                          ElevatedButton(
+                            onPressed: () {
                               setState(() {
-                                if (value == true) {
-                                  selectedResults.add(e);
-                                } else {
-                                  selectedResults.remove(e);
-                                }
+                                _filterName = "";
                               });
                             },
+                            child: const Text("CLEAR"),
+                          ),
+                        ],
+                      ),
+                      Row(
+                        children: [
+                          Expanded(
+                            child: Row(
+                              children: [
+                                const Text("固件"),
+                                Checkbox(
+                                  value: _filterSofewareEquals,
+                                  onChanged: (bool? value) {
+                                    setState(() {
+                                      _filterSofewareEquals = value ?? false;
+                                    });
+                                  },
+                                ),
+                                Expanded(
+                                  child: Padding(
+                                    padding: const EdgeInsets.symmetric(horizontal: 10.0),
+                                    child: TextField(
+                                      keyboardType: const TextInputType.numberWithOptions(decimal: true),
+                                      onChanged: (v) {
+                                        setState(() {
+                                          _filterSofeware = v;
+                                        });
+                                      },
+                                    ),
+                                  ),
+                                ),
+                              ],
+                            ),
                           ),
+                          Expanded(
+                            child: Row(
+                              children: [
+                                const Text("硬件"),
+                                Checkbox(
+                                  value: _filterHardwareEquals,
+                                  onChanged: (bool? value) {
+                                    setState(() {
+                                      _filterHardwareEquals = value ?? false;
+                                    });
+                                  },
+                                ),
+                                Expanded(
+                                  child: Padding(
+                                    padding: const EdgeInsets.symmetric(horizontal: 10.0),
+                                    child: TextField(
+                                      keyboardType: const TextInputType.numberWithOptions(decimal: true),
+                                      onChanged: (v) {
+                                        setState(() {
+                                          _filterHardware = v;
+                                        });
+                                      },
+                                    ),
+                                  ),
+                                ),
+                              ],
+                            ),
+                          ),
+                        ],
+                      ),
+                    ],
+                  ),
+                ),
+                if (scanForDevices == null && items.isEmpty)
+                  Padding(
+                    padding: const EdgeInsets.all(40.0),
+                    child: ElevatedButton(
+                      onPressed: () {
+                        _search();
+                      },
+                      child: const Text("搜索设备"),
+                    ),
+                  ),
+                if (scanForDevices == null)
+                  Padding(
+                    padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
+                    child: Column(
+                      children: [
+                        const Padding(
+                          padding:  EdgeInsets.all(16.0),
+                          child:  Text("升级设备"),
+                        ),
+                        Row(
+                          mainAxisSize: MainAxisSize.min,
+                          children: [
+                            ElevatedButton(
+                              onPressed: () async {
+                                setState(() {
+                                  selectedResults.clear();
+                                  selectedResults.addAll(items);
+                                  _dfuAll = true;
+                                });
+
+                                scanForDevices?.cancel();
+                                clearCache();
+                                FilePickerResult? result = await FilePicker.platform.pickFiles();
+
+                                String? path = result?.files.single.path ?? "";
+                                if (path.isNotEmpty == true) {
+                                  _dfu(path, 0);
+                                }
+                              },
+                              child: const Text("模式 1,选取最优"),
+                            ),
+                            const SizedBox(
+                              width: 20,
+                            ),
+                            ElevatedButton(
+                              onPressed: () async {
+                                setState(() {
+                                  selectedResults.clear();
+                                  selectedResults.addAll(items);
+                                  _dfuAll = true;
+                                });
+
+                                scanForDevices?.cancel();
+                                clearCache();
+                                FilePickerResult? result = await FilePicker.platform.pickFiles();
+
+                                String? path = result?.files.single.path ?? "";
+                                if (path.isNotEmpty == true) {
+                                  _dfu(path, 1);
+                                }
+                              },
+                              child: const Text("模式 2,列表随机"),
+                            ),
+                          ],
+                        ),
                       ],
                     ),
                   ),
-                ),
-              );
-            },
-            itemCount: items.length,
-          )),
-          if (_selectAll)
-            Padding(
-              padding: const EdgeInsets.all(24.0),
-              child: Row(
-                children: [
-                  ElevatedButton(
-                    onPressed: () async {
-                      scanForDevices?.cancel();
-
-                      if (await showDialog(
-                              context: context,
-                              builder: (context) {
-                                return SimpleDialog(
-                                  title: const Text("操作确认"),
+                Expanded(
+                    child: ListView.builder(
+                  itemBuilder: (context, index) {
+                    var e = items[index];
+                    return GestureDetector(
+                      child: Card(
+                        child: Padding(
+                          padding: const EdgeInsets.all(12.0),
+                          child: Row(
+                            children: [
+                              Expanded(
+                                child: Column(
                                   children: [
-                                    if ((_filterHardware?.length ?? 0) == 0)
-                                      const Padding(
-                                        padding: EdgeInsets.all(20.0),
-                                        child: Text("请确保所选设备是同一个硬件版本,排除升级后出现不兼容的情况"),
-                                      ),
-                                    if ((_filterHardware?.length ?? 0) > 0)
-                                      Padding(
-                                        padding: const EdgeInsets.all(20.0),
-                                        child: Text("升级对应的硬件版本 v${_filterHardware}"),
-                                      ),
-                                    Padding(
-                                      padding: const EdgeInsets.all(20.0),
-                                      child: ElevatedButton(
-                                        onPressed: () {
-                                          Navigator.pop(context, true);
-                                        },
-                                        child: const Text("知道了"),
-                                      ),
+                                    Text(
+                                      e.name,
+                                      style: Theme.of(context).textTheme.headline6,
+                                    ),
+                                    ConstrainedBox(constraints: const BoxConstraints(maxWidth: 140.0), child: Text(e.id)),
+                                    Text("${e.manufacturerData}"),
+                                    Row(
+                                      children: [
+                                        Text("${e.rssi} dBm"),
+                                        const SizedBox(
+                                          width: 20,
+                                        ),
+                                        Text("#${index + 1}"),
+                                      ],
                                     ),
                                   ],
+                                  crossAxisAlignment: CrossAxisAlignment.start,
+                                ),
+                              ),
+                              Padding(
+                                padding: const EdgeInsets.symmetric(horizontal: 8.0),
+                                child: ElevatedButton(
+                                  onPressed: () async {
+                                    await showDialog(
+                                      context: context,
+                                      builder: (context) => Find(device: e),
+                                    );
+                                  },
+                                  child: const Text("找鞋"),
+                                ),
+                              ),
+                              if (!_selectAll)
+                                ElevatedButton(
+                                  onPressed: () {
+                                    setState(() {
+                                      scanForDevices?.cancel();
+                                      scanForDevices = null;
+                                    });
+
+                                    Navigator.of(context).push(MaterialPageRoute(builder: (context) {
+                                      return Connector(
+                                        device: e,
+                                      );
+                                    }));
+                                  },
+                                  child: const Text("CONNECT"),
+                                ),
+                              if (_selectAll)
+                                Checkbox(
+                                  value: selectedResults.contains(e),
+                                  onChanged: (bool? value) {
+                                    setState(() {
+                                      if (value == true) {
+                                        selectedResults.add(e);
+                                      } else {
+                                        selectedResults.remove(e);
+                                      }
+                                    });
+                                  },
+                                ),
+                            ],
+                          ),
+                        ),
+                      ),
+                    );
+                  },
+                  itemCount: items.length,
+                )),
+                if (_selectAll)
+                  Padding(
+                    padding: const EdgeInsets.all(24.0),
+                    child: Row(
+                      children: [
+                        ElevatedButton(
+                          onPressed: () async {
+                            scanForDevices?.cancel();
+                            clearCache();
+
+                            if (await showDialog(
+                                    context: context,
+                                    builder: (context) {
+                                      return SimpleDialog(
+                                        title: const Text("操作确认"),
+                                        children: [
+                                          if ((_filterHardware?.length ?? 0) == 0)
+                                            const Padding(
+                                              padding: EdgeInsets.all(20.0),
+                                              child: Text("请确保所选设备是同一个硬件版本,排除升级后出现不兼容的情况"),
+                                            ),
+                                          if ((_filterHardware?.length ?? 0) > 0)
+                                            Padding(
+                                              padding: const EdgeInsets.all(20.0),
+                                              child: Text("升级对应的硬件版本 v${_filterHardware}"),
+                                            ),
+                                          Padding(
+                                            padding: const EdgeInsets.all(20.0),
+                                            child: ElevatedButton(
+                                              onPressed: () {
+                                                Navigator.pop(context, true);
+                                              },
+                                              child: const Text("知道了"),
+                                            ),
+                                          ),
+                                        ],
+                                      );
+                                    }) !=
+                                true) {
+                              return;
+                            }
+
+                            if (selectedResults.isEmpty) {
+                              ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
+                                content: Text('请选择需要升级的设备'),
+                              ));
+                              return;
+                            }
+
+                            FilePickerResult? result = await FilePicker.platform.pickFiles();
+
+                            String? path = result?.files.single.path ?? "";
+                            if (path.isNotEmpty == true) {
+                              File file = File(path);
+                              Navigator.of(context).push(MaterialPageRoute(builder: (context) {
+                                return DFUList(
+                                  selectedResults: selectedResults,
+                                  file: file,
                                 );
-                              }) !=
-                          true) {
-                        return;
-                      }
-
-                      FilePickerResult? result = await FilePicker.platform.pickFiles();
-
-                      String? path = result?.files.single.path ?? "";
-                      if (path.isNotEmpty == true) {
-                        File file = File(path);
-                        Navigator.of(context).push(MaterialPageRoute(builder: (context) {
-                          return DFUList(
-                            selectedResults: selectedResults,
-                            file: file,
-                          );
-                        }));
-                      } else {
-                        // User canceled the picker
-                      }
-                    },
-                    child: Text("批量DFU(${selectedResults.length})"),
-                  ),
-                  const SizedBox(
-                    width: 12,
-                  ),
-                  ElevatedButton(
-                    onPressed: () async {
-                      setState(() {
-                        selectedResults.clear();
-                        selectedResults.addAll(items);
-                      });
-                    },
-                    child: Text("全选"),
-                  ),
-                  const SizedBox(
-                    width: 12,
-                  ),
-                  ElevatedButton(
-                    onPressed: () async {
-                      setState(() {
-                        selectedResults.clear();
-                      });
-                    },
-                    child: Text("全不选"),
+                              }));
+                            } else {
+                              // User canceled the picker
+                            }
+                          },
+                          child: Text("批量DFU(${selectedResults.length})"),
+                        ),
+                        const SizedBox(
+                          width: 12,
+                        ),
+                        ElevatedButton(
+                          onPressed: () async {
+                            setState(() {
+                              selectedResults.clear();
+                              selectedResults.addAll(items);
+                            });
+                          },
+                          child: Text("全选"),
+                        ),
+                        const SizedBox(
+                          width: 12,
+                        ),
+                        ElevatedButton(
+                          onPressed: () async {
+                            setState(() {
+                              selectedResults.clear();
+                            });
+                          },
+                          child: Text("全不选"),
+                        ),
+                      ],
+                    ),
                   ),
-                ],
-              ),
+              ],
             ),
-        ],
-      ),
     );
   }
 }

+ 8 - 2
plugin/nordic_dfu-5.0.1/android/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java

@@ -723,10 +723,16 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory;
 	byte[] readNotificationResponse()
             throws DeviceDisconnectedException, DfuException, UploadAbortedException {
 		// do not clear the mReceiveData here. The response might already be obtained. Clear it in write request instead.
+		long start = System.currentTimeMillis();
 		try {
 			synchronized (mLock) {
-				while ((mReceivedData == null && mConnected && mError == 0 && !mAborted) || mPaused)
-					mLock.wait();
+				while ((mReceivedData == null && mConnected && mError == 0 && !mAborted) || mPaused) {
+					if((System.currentTimeMillis() - start) > 10000){
+						throw new DeviceDisconnectedException("readNotificationResponse timeout");
+					}else {
+						mLock.wait(5000);
+					}
+				}
 			}
 		} catch (final InterruptedException e) {
 			loge("Sleeping interrupted", e);

+ 2 - 2
plugin/nordic_dfu-5.0.1/android/src/main/java/no/nordicsemi/android/dfu/ButtonlessDfuImpl.java

@@ -46,7 +46,7 @@ import no.nordicsemi.android.error.SecureDfuError;
 
 	private static final int OP_CODE_ENTER_BOOTLOADER_KEY = 0x01;
 	private static final int OP_CODE_RESPONSE_CODE_KEY = 0x20;
-	private static final byte[] OP_CODE_ENTER_BOOTLOADER = new byte[]{OP_CODE_ENTER_BOOTLOADER_KEY};
+	private static final byte[] OP_CODE_ENTER_BOOTLOADER = new byte[]{OP_CODE_ENTER_BOOTLOADER_KEY, 0x00};
 
 	ButtonlessDfuImpl(@NonNull final Intent intent, @NonNull final DfuBaseService service) {
 		super(intent, service);
@@ -110,7 +110,7 @@ import no.nordicsemi.android.error.SecureDfuError;
 		try {
 			// Send 'enter bootloader command'
 			mProgressInfo.setProgress(DfuBaseService.PROGRESS_ENABLING_DFU_MODE);
-			logi("Sending Enter Bootloader (Op Code = 1)");
+			logi("Sending Enter Bootloader (Op Code = " + OP_CODE_ENTER_BOOTLOADER[0] + ")");
 			writeOpCode(characteristic, OP_CODE_ENTER_BOOTLOADER, true);
 			mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Enter bootloader sent (Op Code = 1)");
 

+ 2 - 0
plugin/nordic_dfu-5.0.1/android/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java

@@ -929,6 +929,7 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres
 					mConnectionState = STATE_DISCONNECTED;
 					if (mDfuServiceImpl != null)
 						mDfuServiceImpl.getGattCallback().onDisconnected();
+					gatt.close();
 				}
 			} else {
 				if (status == 0x08 /* GATT CONN TIMEOUT */ || status == 0x13 /* GATT CONN TERMINATE PEER USER */)
@@ -940,6 +941,7 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres
 					mConnectionState = STATE_DISCONNECTED;
 					if (mDfuServiceImpl != null)
 						mDfuServiceImpl.getGattCallback().onDisconnected();
+					gatt.close();
 				}
 			}
 

+ 6 - 2
plugin/nordic_dfu-5.0.1/android/src/main/java/no/nordicsemi/android/dfu/SecureDfuImpl.java

@@ -602,6 +602,7 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
 			int attempt = 1;
 			// Each page will be sent in MAX_ATTEMPTS
 			while (mProgressInfo.getAvailableObjectSizeIsBytes() > 0) {
+				long start = System.currentTimeMillis();
 				if (!resumeSendingData) {
 					// Create the Data object
 					final int availableObjectSizeInBytes = mProgressInfo.getAvailableObjectSizeIsBytes();
@@ -612,11 +613,13 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
 					// Waiting until the device is ready to receive the data object.
 					// If prepare data object delay was set in the initiator, the delay will be used
 					// for all data objects.
-					if (prepareObjectDelay > 0 || chunkCount == 0) {
+//					if (prepareObjectDelay > 0 || chunkCount == 0) {
+						if (chunkCount == 0) {
 						mService.waitFor(prepareObjectDelay > 0 ? prepareObjectDelay : 400);
+						logi(prepareObjectDelay + ", " + (System.currentTimeMillis() - start));
 					}
 					mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION,
-                            "Uploading firmware...");
+                            "Uploading firmware... ");
 				} else {
 					mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION,
                             "Resuming uploading firmware...");
@@ -627,6 +630,7 @@ class SecureDfuImpl extends BaseCustomDfuImpl {
 				try {
 					logi("Uploading firmware...");
 					uploadFirmwareImage(mPacketCharacteristic);
+					logi("Uploading firmware finish ..." + (System.currentTimeMillis() - start));
 				} catch (final DeviceDisconnectedException e) {
 					loge("Disconnected while sending data");
 					throw e;

+ 35 - 0
pubspec.lock

@@ -378,6 +378,41 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.1.1"
+  wakelock:
+    dependency: "direct main"
+    description:
+      name: wakelock
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.6.2"
+  wakelock_macos:
+    dependency: transitive
+    description:
+      name: wakelock_macos
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.4.0"
+  wakelock_platform_interface:
+    dependency: transitive
+    description:
+      name: wakelock_platform_interface
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.3.0"
+  wakelock_web:
+    dependency: transitive
+    description:
+      name: wakelock_web
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.4.0"
+  wakelock_windows:
+    dependency: transitive
+    description:
+      name: wakelock_windows
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.2.0"
   win32:
     dependency: transitive
     description:

+ 1 - 0
pubspec.yaml

@@ -42,6 +42,7 @@ dependencies:
   permission_handler: any #权限
   file_picker: any
   path_provider: any #路径
+  wakelock: any
   nordic_dfu: #^5.0.1
     path: plugin/nordic_dfu-5.0.1