import 'dart:async'; import 'dart:io'; import 'package:dio/dio.dart'; import 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; import 'package:flutter/material.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'package:get_it/get_it.dart'; import 'package:nordic_dfu/nordic_dfu.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sport/bean/hardware.dart'; import 'package:sport/provider/bluetooth.dart'; import 'package:sport/services/app_subscription_state.dart'; import 'package:sport/utils/toast.dart'; import 'package:sport/widgets/button_primary.dart'; import 'package:umeng_common_sdk/umeng_common_sdk.dart'; import 'package:wakelock/wakelock.dart'; class DfuUpdatePage extends StatefulWidget { final Hardware hardware; final String version; const DfuUpdatePage({Key? key, required this.hardware,required this.version}) : super(key: key); @override State createState() => _PateState(); } class _PateState extends State with SubscriptionState{ ValueNotifier _msg = ValueNotifier("请稍候..."); ValueNotifier _close = ValueNotifier(false); ValueNotifier _title = ValueNotifier("升级设备"); StreamSubscription? scanSubscription; double _progress = .0; bool _error = false; late Bluetooth bluetooth; @override void initState() { super.initState(); bluetooth = GetIt.I(); final context = this.context; final deviceId = bluetooth.deviceId; if (deviceId == null) { ToastUtil.show("请先连接鞋子!!"); Navigator.of(context).pop(); return; } if (bluetooth.electricityNotifier.value < 10) { ToastUtil.showBottom("鞋子电量不足,请确保电量10%以上!"); Navigator.of(context).pop(); return; } Wakelock.enable(); _update(deviceId).then((value) async { bluetooth.addErr("0", "DFU升级成功", "${widget.version}"); _msg.value = "正在重启鞋子..."; }).catchError((error){ bluetooth.addErr("100", "DFU升级失败", "$error"); print("bluetooth -- dfu -- error $error"); _msg.value = "升级失败!!"; }).whenComplete(() async { if (!_error) { ToastUtil.show("升级成功"); }else{ ToastUtil.show("升级失败!!"); } setState(() { _error = false; }); bluetooth.dfu = false; if(!bluetooth.isConnected) { bluetooth.connectDevice(id: deviceId); } Navigator.of(context).pop(true); await Future.delayed(Duration(seconds: 15)); _close.value = true; }); UmengCommonSdk.onEvent("shoe_dfu", {}); } @override void dispose() { bluetooth.dfu = false; Wakelock.disable(); super.dispose(); scanSubscription?.cancel(); } Future _update(String deviceId) async { List list = widget.hardware.devices ?? []; _msg.value = "下载固件..."; var file = await _download(widget.hardware.file!); if (file == null) { ToastUtil.show("下载固件失败!!"); Navigator.of(context).pop(); return null; } print("bluetooth -- dfu -- $list"); print("bluetooth -- dfu -- start device $deviceId"); bluetooth.dfu = true; for (var i = 0; i < list.length; i++) { String mac = list[i]; int type = list.length - i - 1; _title.value = "升级设备(${i + 1}/${list.length})"; print("bluetooth -- dfu -- file $file"); try { _msg.value = "正在连接设备($mac)..."; if (i == 0) { bool setup = false; _msg.value = "正在断开右鞋连接..."; for (var i = 0; i < 2; i++) { print("bluetooth -- dfu setupDeviceVer start $i"); setup = await bluetooth.setupDeviceVer(type); if (setup == true) { _msg.value = "已断开右鞋连接..."; break; } } if (setup != true) { throw TimeoutException("发送指令超时"); } String name = "${mac.replaceAll(":", "").toUpperCase()}"; _msg.value = "开始搜索$name"; if (Platform.isIOS) { DiscoveredDevice? device = await bluetooth.discoverDevice(deviceName: name); if (device == null) { _msg.value = "搜索超时"; throw TimeoutException("搜索超时$name"); } await bluetooth.disconnectDevice("dfu升级", clearGatt: true); await doDfu(device.id, file, i); } else { await bluetooth.disconnectDevice("dfu升级", clearGatt: true); await doDfu(mac, file, i); } } else { await doDfu(deviceId, file, i); } } catch (e) { print(e); _msg.value = "升级失败: ${e.toString()}"; Future.delayed(Duration(seconds: 3)).then((value) => _close.value = true); throw e; } } return deviceId; } Future _download(String url) async { Directory dir = await getTemporaryDirectory(); File file = File("${dir.path}/${url.hashCode}.zip"); if (!await file.exists()) { file.createSync(); } for (var i = 0; i < 3; i++) { try { var resp = await GetIt.I().download(url, file.path, deleteOnError: true, onReceiveProgress: (index, total) => _msg.value = "下载中 (${index ~/ total * 100}%)...", options: CacheOptions(store: null, policy: CachePolicy.noCache).toOptions()); if (resp.data != null) return file.path; } catch (e) { print(e); } } return null; } Future doDfu(String deviceId, String filePath, int index) async { _msg.value = "正在准备升级服务..."; await Future.delayed(Duration(seconds: 3)); // String label = index == 0 ? "右鞋" : "左鞋"; return await NordicDfu().startDfu( deviceId, filePath, fileInAsset: false, onEnablingDfuMode: (deviceAddress) { _msg.value = "准备升级($deviceAddress)..."; }, onDfuProcessStarted: (deviceAddress) { _msg.value = "准备升级($deviceAddress)..."; }, onDfuProcessStarting: (deviceAddress) { _msg.value = "准备升级($deviceAddress)..."; }, onDeviceConnecting: (deviceAddress) { _msg.value = "准备升级($deviceAddress)..."; }, onDeviceConnected: (deviceAddress) { _msg.value = "准备升级($deviceAddress)"; }, onDfuCompleted: (deviceAddress) { _msg.value = "升级完成($deviceAddress)"; }, onFirmwareValidating: (deviceAddress) { _msg.value = "正在校验($deviceAddress)..."; }, onProgressChanged: ( deviceAddress, percent, speed, avgSpeed, currentPart, partsTotal, ) { print('deviceAddress: $deviceAddress, percent: $percent'); double progress = (percent / 200) + index * .5; setState(() { _progress = progress; }); _msg.value = "升级中 (${(progress * 100).toInt()}%)..."; }, onError: ( String? deviceAddress, int? error, int? errorType, String? message, ) { throw Exception("$deviceAddress, $error $errorType, $message"); }, ); } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { return _error; }, child: Padding( padding: const EdgeInsets.all(12.0), child: Column( children: [ ValueListenableBuilder( valueListenable: _title, builder: (BuildContext context, String value, Widget? child) => Text( value, style: Theme.of(context).textTheme.headline3, )), Padding( padding: const EdgeInsets.fromLTRB(15.0, 40.0, 15.0, 15.0), child: SizedBox( height: 5, child: LinearProgressIndicator( value: _progress <= 0 ? null : _progress, backgroundColor: const Color(0xfff1f1f1), )), ), ValueListenableBuilder( valueListenable: _msg, builder: (BuildContext context, String value, Widget? child) => Text( value, style: Theme.of(context).textTheme.bodyText1, )), ValueListenableBuilder( valueListenable: _close, builder: (BuildContext context, bool value, Widget? child) => value ? Container( width: 140, margin: EdgeInsets.only(top: 12.0), child: PrimaryButton( callback: () { Navigator.pop(context, false); }, content: "关闭"), ) : Container()) ], ), ), ); } }