123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784 |
- 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);
- @override
- State<StatefulWidget> createState() => _State();
- }
- class _State extends State<Scanner> with SubscriptionState {
- final flutterReactiveBle = FlutterReactiveBle();
- bool _selectAll = false;
- double _filterRssi = 35;
- bool _filterNameEquals = true;
- bool _filterSofewareEquals = true;
- bool _filterHardwareEquals = true;
- String? _filterName = "";
- String? _filterSofeware = "";
- String? _filterHardware = "";
- StreamSubscription? scanForDevices;
- 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();
- addSubscription(flutterReactiveBle.statusStream.listen((event) {
- print("bluetooth -- instance state $event");
- if (event == BleStatus.ready) {
- } else if (event == BleStatus.unknown) {
- } else if (event == BleStatus.poweredOff) {
- } else {}
- }));
- _grant();
- clearCache();
- Wakelock.enable();
- }
- _grant() async {
- if (Platform.isAndroid) {
- PermissionStatus permissions = await Permission.locationWhenInUse.request();
- if (permissions.isGranted) {
- } else {
- showDialog(
- context: context,
- builder: (context) => const SimpleDialog(title: Text('使用蓝牙功能需授权定位权限')),
- );
- }
- }
- }
- _search() {
- FocusScope.of(context).requestFocus(FocusNode());
- scanForDevices?.cancel();
- setState(() {
- selectedResults.clear();
- scanResults.clear();
- });
- 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;
- } else {
- scanResults.add(e);
- }
- setState(() {});
- })
- ..onDone(() {
- setState(() {
- scanForDevices = null;
- });
- });
- }
- 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>[];
- items.addAll(scanResults.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;
- }));
- items.sort((a, b) {
- return a.rssi.abs() > b.rssi.abs() ? 1 : -1;
- });
- return Scaffold(
- appBar: _dfuAll
- ? null
- : 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: _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(
- height: 25,
- ),
- ConstrainedBox(
- constraints: const BoxConstraints(maxWidth: 140.0),
- child: Text(
- "${_targetDevice?.id}",
- style: const TextStyle(fontSize: 12, color: Color(0xff999999)),
- ),
- ),
- 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,
- ),
- ),
- 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("rssi"),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 8.0),
- child: Text("< ${_filterRssi.toInt()}"),
- ),
- Expanded(
- child: Slider(
- min: 20,
- 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(
- onPressed: () {
- setState(() {
- _filterName = "FUN_";
- });
- },
- child: const Text("FUN"),
- ),
- const SizedBox(
- width: 10,
- ),
- ElevatedButton(
- onPressed: () {
- setState(() {
- _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,列表随机"),
- ),
- ],
- ),
- ],
- ),
- ),
- 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;
- });
- 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,
- );
- }));
- } 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("全不选"),
- ),
- ],
- ),
- ),
- ],
- ),
- );
- }
- }
|