dfu_update_page.dart 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:dio/dio.dart';
  4. import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
  7. import 'package:get_it/get_it.dart';
  8. import 'package:nordic_dfu/nordic_dfu.dart';
  9. import 'package:path_provider/path_provider.dart';
  10. import 'package:sport/bean/hardware.dart';
  11. import 'package:sport/provider/bluetooth.dart';
  12. import 'package:sport/services/app_subscription_state.dart';
  13. import 'package:sport/utils/toast.dart';
  14. import 'package:sport/widgets/button_primary.dart';
  15. import 'package:umeng_common_sdk/umeng_common_sdk.dart';
  16. import 'package:wakelock/wakelock.dart';
  17. class DfuUpdatePage extends StatefulWidget {
  18. final Hardware hardware;
  19. final String version;
  20. const DfuUpdatePage({Key? key, required this.hardware,required this.version}) : super(key: key);
  21. @override
  22. State<StatefulWidget> createState() => _PateState();
  23. }
  24. class _PateState extends State<DfuUpdatePage> with SubscriptionState{
  25. ValueNotifier<String> _msg = ValueNotifier<String>("请稍候...");
  26. ValueNotifier<bool> _close = ValueNotifier<bool>(false);
  27. ValueNotifier<String> _title = ValueNotifier<String>("升级设备");
  28. StreamSubscription? scanSubscription;
  29. double _progress = .0;
  30. bool _error = false;
  31. late Bluetooth bluetooth;
  32. @override
  33. void initState() {
  34. super.initState();
  35. bluetooth = GetIt.I<Bluetooth>();
  36. final context = this.context;
  37. final deviceId = bluetooth.deviceId;
  38. if (deviceId == null) {
  39. ToastUtil.show("请先连接鞋子!!");
  40. Navigator.of(context).pop();
  41. return;
  42. }
  43. if (bluetooth.electricityNotifier.value < 10) {
  44. ToastUtil.showBottom("鞋子电量不足,请确保电量10%以上!");
  45. Navigator.of(context).pop();
  46. return;
  47. }
  48. Wakelock.enable();
  49. _update(deviceId).then((value) async {
  50. bluetooth.addErr("0", "DFU升级成功", "${widget.version}");
  51. _msg.value = "正在重启鞋子...";
  52. }).catchError((error){
  53. bluetooth.addErr("100", "DFU升级失败", "$error");
  54. print("bluetooth -- dfu -- error $error");
  55. _msg.value = "升级失败!!";
  56. }).whenComplete(() async {
  57. if (!_error) {
  58. ToastUtil.show("升级成功");
  59. }else{
  60. ToastUtil.show("升级失败!!");
  61. }
  62. setState(() {
  63. _error = false;
  64. });
  65. bluetooth.dfu = false;
  66. if(!bluetooth.isConnected) {
  67. bluetooth.connectDevice(id: deviceId);
  68. }
  69. Navigator.of(context).pop(true);
  70. await Future.delayed(Duration(seconds: 15));
  71. _close.value = true;
  72. });
  73. UmengCommonSdk.onEvent("shoe_dfu", {});
  74. }
  75. @override
  76. void dispose() {
  77. bluetooth.dfu = false;
  78. Wakelock.disable();
  79. super.dispose();
  80. scanSubscription?.cancel();
  81. }
  82. Future<String?> _update(String deviceId) async {
  83. List<String> list = widget.hardware.devices ?? [];
  84. _msg.value = "下载固件...";
  85. var file = await _download(widget.hardware.file!);
  86. if (file == null) {
  87. ToastUtil.show("下载固件失败!!");
  88. Navigator.of(context).pop();
  89. return null;
  90. }
  91. print("bluetooth -- dfu -- $list");
  92. print("bluetooth -- dfu -- start device $deviceId");
  93. bluetooth.dfu = true;
  94. for (var i = 0; i < list.length; i++) {
  95. String mac = list[i];
  96. int type = list.length - i - 1;
  97. _title.value = "升级设备(${i + 1}/${list.length})";
  98. print("bluetooth -- dfu -- file $file");
  99. try {
  100. _msg.value = "正在连接设备($mac)...";
  101. if (i == 0) {
  102. bool setup = false;
  103. _msg.value = "正在断开右鞋连接...";
  104. for (var i = 0; i < 2; i++) {
  105. print("bluetooth -- dfu setupDeviceVer start $i");
  106. setup = await bluetooth.setupDeviceVer(type);
  107. if (setup == true) {
  108. _msg.value = "已断开右鞋连接...";
  109. break;
  110. }
  111. }
  112. if (setup != true) {
  113. throw TimeoutException("发送指令超时");
  114. }
  115. String name = "${mac.replaceAll(":", "").toUpperCase()}";
  116. _msg.value = "开始搜索$name";
  117. if (Platform.isIOS) {
  118. DiscoveredDevice? device = await bluetooth.discoverDevice(deviceName: name);
  119. if (device == null) {
  120. _msg.value = "搜索超时";
  121. throw TimeoutException("搜索超时$name");
  122. }
  123. await bluetooth.disconnectDevice("dfu升级", clearGatt: true);
  124. await doDfu(device.id, file, i);
  125. } else {
  126. await bluetooth.disconnectDevice("dfu升级", clearGatt: true);
  127. await doDfu(mac, file, i);
  128. }
  129. } else {
  130. await doDfu(deviceId, file, i);
  131. }
  132. } catch (e) {
  133. print(e);
  134. _msg.value = "升级失败: ${e.toString()}";
  135. Future.delayed(Duration(seconds: 3)).then((value) => _close.value = true);
  136. throw e;
  137. }
  138. }
  139. return deviceId;
  140. }
  141. Future<String?> _download(String url) async {
  142. Directory dir = await getTemporaryDirectory();
  143. File file = File("${dir.path}/${url.hashCode}.zip");
  144. if (!await file.exists()) {
  145. file.createSync();
  146. }
  147. for (var i = 0; i < 3; i++) {
  148. try {
  149. var resp = await GetIt.I<Dio>().download(url, file.path, deleteOnError: true, onReceiveProgress: (index, total) => _msg.value = "下载中 (${index ~/ total * 100}%)...", options: CacheOptions(store: null, policy: CachePolicy.noCache).toOptions());
  150. if (resp.data != null) return file.path;
  151. } catch (e) {
  152. print(e);
  153. }
  154. }
  155. return null;
  156. }
  157. Future<String?> doDfu(String deviceId, String filePath, int index) async {
  158. _msg.value = "正在准备升级服务...";
  159. await Future.delayed(Duration(seconds: 3));
  160. // String label = index == 0 ? "右鞋" : "左鞋";
  161. return await NordicDfu().startDfu(
  162. deviceId,
  163. filePath,
  164. fileInAsset: false,
  165. onEnablingDfuMode: (deviceAddress) {
  166. _msg.value = "准备升级($deviceAddress)...";
  167. },
  168. onDfuProcessStarted: (deviceAddress) {
  169. _msg.value = "准备升级($deviceAddress)...";
  170. },
  171. onDfuProcessStarting: (deviceAddress) {
  172. _msg.value = "准备升级($deviceAddress)...";
  173. },
  174. onDeviceConnecting: (deviceAddress) {
  175. _msg.value = "准备升级($deviceAddress)...";
  176. },
  177. onDeviceConnected: (deviceAddress) {
  178. _msg.value = "准备升级($deviceAddress)";
  179. },
  180. onDfuCompleted: (deviceAddress) {
  181. _msg.value = "升级完成($deviceAddress)";
  182. },
  183. onFirmwareValidating: (deviceAddress) {
  184. _msg.value = "正在校验($deviceAddress)...";
  185. },
  186. onProgressChanged: (
  187. deviceAddress,
  188. percent,
  189. speed,
  190. avgSpeed,
  191. currentPart,
  192. partsTotal,
  193. ) {
  194. print('deviceAddress: $deviceAddress, percent: $percent');
  195. double progress = (percent / 200) + index * .5;
  196. setState(() {
  197. _progress = progress;
  198. });
  199. _msg.value = "升级中 (${(progress * 100).toInt()}%)...";
  200. },
  201. onError: (
  202. String? deviceAddress,
  203. int? error,
  204. int? errorType,
  205. String? message,
  206. ) {
  207. throw Exception("$deviceAddress, $error $errorType, $message");
  208. },
  209. );
  210. }
  211. @override
  212. Widget build(BuildContext context) {
  213. return WillPopScope(
  214. onWillPop: () async {
  215. return _error;
  216. },
  217. child: Padding(
  218. padding: const EdgeInsets.all(12.0),
  219. child: Column(
  220. children: <Widget>[
  221. ValueListenableBuilder(
  222. valueListenable: _title,
  223. builder: (BuildContext context, String value, Widget? child) => Text(
  224. value,
  225. style: Theme.of(context).textTheme.headline3,
  226. )),
  227. Padding(
  228. padding: const EdgeInsets.fromLTRB(15.0, 40.0, 15.0, 15.0),
  229. child: SizedBox(
  230. height: 5,
  231. child: LinearProgressIndicator(
  232. value: _progress <= 0 ? null : _progress,
  233. backgroundColor: const Color(0xfff1f1f1),
  234. )),
  235. ),
  236. ValueListenableBuilder(
  237. valueListenable: _msg,
  238. builder: (BuildContext context, String value, Widget? child) => Text(
  239. value,
  240. style: Theme.of(context).textTheme.bodyText1,
  241. )),
  242. ValueListenableBuilder(
  243. valueListenable: _close,
  244. builder: (BuildContext context, bool value, Widget? child) => value
  245. ? Container(
  246. width: 140,
  247. margin: EdgeInsets.only(top: 12.0),
  248. child: PrimaryButton(
  249. callback: () {
  250. Navigator.pop(context, false);
  251. },
  252. content: "关闭"),
  253. )
  254. : Container())
  255. ],
  256. ),
  257. ),
  258. );
  259. }
  260. }