You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
496 lines
15 KiB
496 lines
15 KiB
import 'package:classic_bluetooth/classic_bluetooth.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:path/path.dart';
|
|
import 'dart:async';
|
|
import '../../../data/services/network/api.dart';
|
|
import '../model/ota_upgrade_model.dart';
|
|
import 'dart:io';
|
|
import 'package:dio/dio.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:ota/ota.dart';
|
|
import '../../../data/services/ble_manager.dart';
|
|
|
|
class OtaUpgradeController extends GetxController {
|
|
static const int NOT_STARTED = -1; // 未开始
|
|
static const int VALIDATION_FILE = 0; // 验证文件
|
|
static const int ONUPGRADE = 1; // 升级中
|
|
static const int START_UPGRADING = 2; // 开始升级
|
|
static const int UPGRADE_SUCCESS = 3; // 升级成功
|
|
static const int UPGRADE_FAILED = 4; // 升级失败
|
|
static const int CONNECTION_DISCONNECTION = 5; // 连接断开
|
|
static const int CONNECTION_SUCCESS = 6; // 连接成功
|
|
static const int DOWNLOADING_UP_FILES = 7; // 下载升级文件
|
|
|
|
// 错误信息,用于存储错误提示
|
|
final RxString errorMessage = ''.obs;
|
|
|
|
// 最新版本号
|
|
final RxString latestVersion = ''.obs;
|
|
// 最新更新内容
|
|
final RxString updatedescription = ''.obs;
|
|
|
|
// 当前设备固件版本号
|
|
final RxString currentVersion = ''.obs;
|
|
|
|
String _address = '';
|
|
|
|
// 更新包地址,用于下载更新文件
|
|
String updatepackageaddress = '';
|
|
|
|
// 下载进度,范围为 0.0 到 1.0
|
|
final RxDouble downloadProgress = 0.0.obs;
|
|
|
|
// 是否有可用更新的状态
|
|
final isUpdateAvailable = true.obs;
|
|
|
|
// 当前升级进度,范围为 0.0 到 1.0
|
|
// final RxDouble progress = 0.0.obs;
|
|
|
|
// OTA 单例实例,用于处理升级相关逻辑
|
|
final jlota = Ota.instance;
|
|
|
|
// 是否正在下载更新包的状态
|
|
final RxBool isDownloading = false.obs;
|
|
|
|
// 蓝牙管理服务,用于获取设备信息和通信
|
|
final _bleManager = Get.find<BleManager>();
|
|
final maxRetryAttempts = 3; // 最大重试次数
|
|
int currentRetryCount = 0;
|
|
|
|
final RxBool isFetchingDeviceInfo = true.obs;
|
|
|
|
final RxDouble progress = 0.0.obs; // 控制器自己的可观察属性
|
|
final RxInt type = OtaEvent.NOT_STARTED.obs;
|
|
final RxBool isUpgrade = false.obs;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
_getCurrentVersion();
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
jlota.onOtaClose();
|
|
_bleManager.setEnterOta(false);
|
|
isUpgrade.value = false;
|
|
super.onClose();
|
|
}
|
|
|
|
// 添加重试方法
|
|
void retryConnection() {
|
|
errorMessage.value = '';
|
|
_getCurrentVersion();
|
|
}
|
|
|
|
String getStatusMessage(int type) {
|
|
switch (type) {
|
|
case NOT_STARTED: // -1
|
|
return "未开始".tr;
|
|
case VALIDATION_FILE: // 0
|
|
return "验证文件中".tr;
|
|
case ONUPGRADE: // 1
|
|
return "升级中".tr;
|
|
case START_UPGRADING: // 2
|
|
return "开始升级".tr;
|
|
case UPGRADE_SUCCESS: // 3
|
|
return "升级成功".tr;
|
|
case UPGRADE_FAILED: // 4
|
|
return "升级失败".tr;
|
|
case CONNECTION_DISCONNECTION: // 5
|
|
return "连接断开".tr;
|
|
case CONNECTION_SUCCESS: // 6
|
|
return "连接成功".tr;
|
|
case DOWNLOADING_UP_FILES: // 7 (新增)
|
|
return "下载升级文件".tr;
|
|
default:
|
|
return "未知状态".tr; // 更安全的默认值
|
|
}
|
|
}
|
|
|
|
// 添加更新方法
|
|
Future<void> startUpdate() async {
|
|
try {
|
|
checkUpdate();
|
|
|
|
// 实现更新逻辑...
|
|
} catch (e) {
|
|
errorMessage.value = e.toString();
|
|
Get.snackbar('更新失败', e.toString());
|
|
}
|
|
}
|
|
|
|
Future<void> _getCurrentVersion() async {
|
|
_listenToEvents();
|
|
try {
|
|
isFetchingDeviceInfo.value = true;
|
|
errorMessage.value = '';
|
|
// 添加获取设备信息的重试机制
|
|
DeviceInfo? deviceInfo = _bleManager.deviceInfo.value;
|
|
|
|
// // 第一次检查设备信息
|
|
// if (deviceInfo == null) {
|
|
// // 尝试重新获取设备信息
|
|
// await _bleManager.fetchDeviceInfo();
|
|
// }
|
|
|
|
while (currentRetryCount < maxRetryAttempts) {
|
|
deviceInfo = _bleManager.deviceInfo.value;
|
|
|
|
// 检查信息有效性
|
|
if (deviceInfo != null &&
|
|
deviceInfo.firmwareVersion.isNotEmpty &&
|
|
deviceInfo.firmwareProductId.isNotEmpty) {
|
|
break;
|
|
}
|
|
|
|
// 获取设备信息
|
|
await _bleManager.fetchDeviceInfo();
|
|
await Future.delayed(const Duration(seconds: 5));
|
|
currentRetryCount++;
|
|
}
|
|
// 最终验证
|
|
if (deviceInfo == null ||
|
|
deviceInfo.firmwareVersion.isEmpty ||
|
|
deviceInfo.firmwareProductId.isEmpty) {
|
|
throw "获取设备信息失败,请检查连接";
|
|
}
|
|
// 拆分 firmwareVersion 并判断两边是否一致
|
|
final firmwareParts = deviceInfo.firmwareVersion.split('.');
|
|
|
|
// 从第三个 `.` 开始分割
|
|
final firstPart = firmwareParts.sublist(0, 3).join('.');
|
|
final secondPart = firmwareParts.sublist(3).join('.');
|
|
print("firstPart${firstPart},secondPart=${secondPart}");
|
|
// 判断两部分是否一致
|
|
if (firstPart != secondPart) {
|
|
throw "固件版本格式错误:${deviceInfo.firmwareVersion}";
|
|
}
|
|
|
|
// 正常流程
|
|
currentVersion.value = firstPart;
|
|
print('getequipmentinfo,id: ${deviceInfo.firmwareProductId}');
|
|
|
|
final connectedDevices = await ClassicBluetooth.getConnectedDevices();
|
|
if (connectedDevices.isNotEmpty) {
|
|
_address = connectedDevices.first.address;
|
|
}
|
|
|
|
await _bleManager.setEnterOta(true);
|
|
|
|
await Future.delayed(const Duration(seconds: 2));
|
|
|
|
jlota.init();
|
|
|
|
// API请求
|
|
final response = await Api.getequipmentinfo({
|
|
"id": deviceInfo.firmwareProductId,
|
|
});
|
|
|
|
// 处理API响应
|
|
if (response['info'] == null) {
|
|
throw const FormatException('API 响应格式错误');
|
|
}
|
|
|
|
final resp = OtaConfigResp.fromJson(response);
|
|
print('getequipmentinfo,resp.systemversion: ${resp.systemversion}');
|
|
latestVersion.value = resp.systemversion!;
|
|
updatedescription.value = resp.updatedescription!;
|
|
updatepackageaddress = resp.updatepackageaddress!;
|
|
|
|
// 版本检查
|
|
isUpdateAvailable.value = _isNewVersionAvailable(resp.systemversion!);
|
|
} catch (e) {
|
|
// 统一错误处理
|
|
errorMessage.value = e.toString();
|
|
Get.snackbar('错误', e.toString(),
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.red,
|
|
colorText: Colors.white);
|
|
// 重要:重新抛出异常以便上层处理(可选)
|
|
} finally {
|
|
isFetchingDeviceInfo.value = false;
|
|
}
|
|
}
|
|
|
|
// 版本比较方法优化
|
|
bool _isNewVersionAvailable(String latest) {
|
|
try {
|
|
List<int> currentParts = currentVersion.value
|
|
.split('.')
|
|
.map((e) => int.parse(e.trim()))
|
|
.toList();
|
|
List<int> latestParts =
|
|
latest.split('.').map((e) => int.parse(e.trim())).toList();
|
|
|
|
for (int i = 0; i < latestParts.length; i++) {
|
|
int current = (i < currentParts.length) ? currentParts[i] : 0;
|
|
if (latestParts[i] > current) return true;
|
|
if (latestParts[i] < current) return false;
|
|
}
|
|
return false;
|
|
} catch (e) {
|
|
print('版本比较错误: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 版本更新
|
|
Future<void> checkUpdate() async {
|
|
try {
|
|
if (latestVersion.value != "") {
|
|
_showUpdateDialog(latestVersion.value);
|
|
} else {
|
|
Get.snackbar('错误', '获取最新版本信息失败');
|
|
}
|
|
} catch (e) {
|
|
Get.snackbar('错误', '获取版本信息失败');
|
|
}
|
|
}
|
|
|
|
void _showUpdateDialog(String latest) async {
|
|
print('显示更新对话框');
|
|
Get.dialog(
|
|
AlertDialog(
|
|
title: Text('发现新版本 $latest'),
|
|
content: Text('是否立即更新?'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () async {
|
|
print('取消按钮被点击');
|
|
|
|
Get.back();
|
|
Future.delayed(Duration(milliseconds: 300));
|
|
},
|
|
child: Text('取消'),
|
|
),
|
|
TextButton(
|
|
onPressed: () async {
|
|
print('更新按钮被点击');
|
|
|
|
Get.back();
|
|
Future.delayed(Duration(milliseconds: 300));
|
|
|
|
// 检查设备电量
|
|
if (!_checkBatteryLevel()) {
|
|
return; // 电量不足,不执行升级
|
|
}
|
|
|
|
startDownload();
|
|
},
|
|
child: Text('更新'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// 修改后的startDownload方法
|
|
Future<void> startDownload() async {
|
|
isDownloading.value = true;
|
|
int lastIndex = updatepackageaddress.lastIndexOf('/');
|
|
String fileName = updatepackageaddress.substring(lastIndex + 1);
|
|
String directoryName = "ota_upgrades";
|
|
|
|
final dio = Dio(BaseOptions(
|
|
connectTimeout: const Duration(seconds: 5),
|
|
receiveTimeout: Duration.zero,
|
|
sendTimeout: const Duration(seconds: 5),
|
|
headers: {'Accept-Encoding': 'gzip'},
|
|
));
|
|
|
|
type.value = DOWNLOADING_UP_FILES;
|
|
try {
|
|
final Directory? downloadsDir = await getApplicationSupportDirectory();
|
|
if (downloadsDir == null) {
|
|
throw FileSystemException('无法访问下载目录');
|
|
}
|
|
|
|
final targetDir =
|
|
Directory('${downloadsDir.path}/Download/$directoryName');
|
|
if (!await targetDir.exists()) {
|
|
await targetDir.create(recursive: true);
|
|
}
|
|
|
|
final savePath = '${targetDir.path}/$fileName';
|
|
|
|
// 新增:检查同名文件逻辑
|
|
bool existingSameFile = false;
|
|
try {
|
|
var files = await targetDir.list().toList();
|
|
for (var entity in files) {
|
|
if (await FileSystemEntity.isFile(entity.path)) {
|
|
String name = basename(entity.path); // 使用path包中的basename
|
|
if (name == fileName) {
|
|
existingSameFile = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Get.snackbar('错误', '检查本地文件失败: $e');
|
|
|
|
return;
|
|
}
|
|
|
|
if (existingSameFile) {
|
|
//Get.snackbar('文件已存在', '跳过下载,直接更新');
|
|
|
|
startUpgrading(savePath);
|
|
return;
|
|
}
|
|
|
|
// 新增:删除其他文件逻辑
|
|
try {
|
|
var files = await targetDir.list().toList();
|
|
for (var entity in files) {
|
|
if (await FileSystemEntity.isFile(entity.path)) {
|
|
await entity.delete();
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Get.snackbar('错误', '删除旧文件失败: $e');
|
|
//isUpgrade.value = false;
|
|
return;
|
|
}
|
|
|
|
print('文件保存路径: $savePath');
|
|
|
|
final options = Options(
|
|
receiveTimeout: Duration.zero,
|
|
followRedirects: true,
|
|
maxRedirects: 5,
|
|
);
|
|
// 修改下载部分
|
|
await dio.download(
|
|
updatepackageaddress,
|
|
savePath,
|
|
options: options,
|
|
onReceiveProgress: (received, total) {
|
|
if (total != -1) {
|
|
downloadProgress.value = received / total;
|
|
}
|
|
},
|
|
);
|
|
File file = File(savePath);
|
|
if (await file.exists()) {
|
|
// Get.snackbar('下载完成', '文件已保存至: $savePath');
|
|
} else {
|
|
Get.snackbar('错误', '文件未找到,请检查写入权限');
|
|
}
|
|
isDownloading.value = false;
|
|
startUpgrading(savePath);
|
|
return;
|
|
} on DioException catch (e) {
|
|
Get.snackbar('下载失败', '错误: ${e.message}');
|
|
} on FileSystemException catch (e) {
|
|
Get.snackbar('存储失败', '无法写入文件: ${e.message}');
|
|
} catch (e) {
|
|
Get.snackbar('未知错误', e.toString());
|
|
}
|
|
}
|
|
|
|
// 开始升级
|
|
void startUpgrading(String path) async {
|
|
jlota.startUpgrading(address: _address, filePath: path);
|
|
return;
|
|
// isUpgrade.value = false;
|
|
}
|
|
|
|
// 停止升级
|
|
void stopUpgrading() async {
|
|
jlota.onOtaClose();
|
|
return;
|
|
// isUpgrade.value = false;
|
|
}
|
|
|
|
// 检查设备电量是否满足升级条件
|
|
bool _checkBatteryLevel() {
|
|
try {
|
|
final deviceInfo = _bleManager.deviceInfo.value;
|
|
|
|
if (deviceInfo == null) {
|
|
Get.snackbar(
|
|
'电量检查失败',
|
|
'无法获取设备信息,请检查设备连接',
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.orange,
|
|
colorText: Colors.white,
|
|
);
|
|
return false;
|
|
}
|
|
|
|
final leftBattery = deviceInfo.leftEarBattery;
|
|
final rightBattery = deviceInfo.rightEarBattery;
|
|
|
|
// 检查左右耳电量是否都大于30%
|
|
if (leftBattery <= 30 || rightBattery <= 30) {
|
|
String message = '设备电量不足,无法进行固件升级\n';
|
|
message += '升级要求:左右耳电量均需大于30%\n';
|
|
message += '当前电量:左耳 ${leftBattery}%,右耳 ${rightBattery}%\n';
|
|
message += '请充电后再试';
|
|
|
|
Get.dialog(
|
|
AlertDialog(
|
|
title: Text('电量不足'),
|
|
content: Text(message),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Get.back(),
|
|
child: Text('确定'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
} catch (e) {
|
|
Get.snackbar(
|
|
'电量检查失败',
|
|
'检查设备电量时发生错误:$e',
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.red,
|
|
colorText: Colors.white,
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void _listenToEvents() {
|
|
Ota.instance.eventStream.listen((event) {
|
|
if (event.event == 'onProgress' &&
|
|
event.type != null &&
|
|
event.progress != null) {
|
|
progress.value = event.progress!;
|
|
type.value = event.type!;
|
|
isUpgrade.value = true; // 开始升级
|
|
}
|
|
if (event.event == 'onStartOTA') {
|
|
type.value = OtaEvent.START_UPGRADING;
|
|
isUpgrade.value = true; // 开始升级
|
|
}
|
|
if (event.event == 'onStopOTA') {
|
|
type.value = OtaEvent.UPGRADE_SUCCESS;
|
|
isUpgrade.value = false;
|
|
Get.snackbar('升级成功', '请退出重新连接设备');
|
|
_getCurrentVersion();
|
|
}
|
|
if (event.event == 'onError') {
|
|
type.value = OtaEvent.UPGRADE_FAILED;
|
|
isUpgrade.value = false;
|
|
Get.snackbar('升级失败', '请检查设备连接或升级包,错误码:${event.code}');
|
|
}
|
|
if (event.event == 'NOCONNECTION') {
|
|
type.value = OtaEvent.CONNECTION_DISCONNECTION;
|
|
isUpgrade.value = false;
|
|
}
|
|
if (event.event == 'CONNECTION_SUCCESS') {
|
|
type.value = OtaEvent.CONNECTION_SUCCESS;
|
|
isUpgrade.value = true; // 开始升级
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|