Browse Source

优化ota升级代码结构,去处ssl证书验证

newdev_shunjiawei
fdp 1 year ago
parent
commit
0ea6bd419c
  1. 872
      lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
  2. 99
      lib/modules/ota_upgrade/services/device_checker.dart
  3. 153
      lib/modules/ota_upgrade/services/download_manager.dart
  4. 43
      lib/modules/ota_upgrade/services/ota_status_manager.dart
  5. 117
      lib/modules/ota_upgrade/services/ui_manager.dart
  6. 114
      lib/modules/ota_upgrade/services/version_manager.dart

872
lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart

@ -1,747 +1,293 @@
import 'package:classic_bluetooth/classic_bluetooth.dart'; import 'package:classic_bluetooth/classic_bluetooth.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:voitrans/data/models/appconfig_model.dart';
import '/routes/app_routes.dart';
import 'package:logger/web.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:path/path.dart'; import 'package:logger/web.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 'package:ota/ota.dart';
import '../../../data/services/ble_manager.dart'; import '../../../data/services/ble_manager.dart';
import '../../../data/models/appconfig.dart'; import '../services/version_manager.dart';
import '../services/download_manager.dart';
import '../services/device_checker.dart';
import '../services/ui_manager.dart';
import '../services/ota_status_manager.dart';
/// OTA升级主控制器 - 重构后的简化版本
/// 负责协调各个服务,管理升级流程的整体状态
class OtaUpgradeController extends GetxController { class OtaUpgradeController extends GetxController {
static const int NOT_STARTED = -1; // 未开始 // 服务依赖
static const int VALIDATION_FILE = 0; // 验证文件 final _versionManager = VersionManager();
static const int ONUPGRADE = 1; // 升级中 final _downloadManager = DownloadManager();
static const int START_UPGRADING = 2; // 开始升级 final _deviceChecker = DeviceChecker();
static const int UPGRADE_SUCCESS = 3; // 升级成功 final _uiManager = UiManager();
static const int UPGRADE_FAILED = 4; // 升级失败 final _bleManager = Get.find<BleManager>();
static const int CONNECTION_DISCONNECTION = 5; // 连接断开 final _jlota = Ota.instance;
static const int CONNECTION_SUCCESS = 6; // 连接成功
static const int DOWNLOADING_UP_FILES = 7; // 下载升级文件
// 错误信息,用于存储错误提示
final RxString errorMessage = ''.obs;
// 最新版本号 // 状态管理
final RxString errorMessage = ''.obs;
final RxString latestVersion = ''.obs; final RxString latestVersion = ''.obs;
// 最新更新内容
final RxString updatedescription = ''.obs;
// 当前设备固件版本号
final RxString currentVersion = ''.obs; final RxString currentVersion = ''.obs;
final RxString detailedStatus = ''.obs;
String _address = ''; final RxString updatedescription = ''.obs; // 添加缺失的更新描述属性
// 更新包地址,用于下载更新文件
String updatepackageaddress = '';
// 下载进度,范围为 0.0 到 1.0
final RxDouble downloadProgress = 0.0.obs; final RxDouble downloadProgress = 0.0.obs;
final RxDouble progress = 0.0.obs;
// 是否有可用更新的状态 final RxInt type = OtaStatusManager.NOT_STARTED.obs;
final isUpdateAvailable = true.obs; final RxBool isUpdateAvailable = true.obs;
// 当前升级进度,范围为 0.0 到 1.0
// final RxDouble progress = 0.0.obs;
// OTA 单例实例,用于处理升级相关逻辑
final jlota = Ota.instance;
// 是否正在下载更新包的状态
final RxBool isDownloading = false.obs; 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; final RxBool isUpgrade = false.obs;
final RxBool isFetchingDeviceInfo = true.obs;
// 新增:详细状态提示
final RxString detailedStatus = ''.obs;
final RxBool showDetailedStatus = false.obs; final RxBool showDetailedStatus = false.obs;
String _address = '';
String _updatePackageAddress = '';
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
_getCurrentVersion(); _initializeController();
} }
@override @override
void onClose() { void onClose() {
jlota.onOtaClose(); _jlota.onOtaClose();
_bleManager.setEnterOta(false); _bleManager.setEnterOta(false);
isUpgrade.value = false; isUpgrade.value = false;
super.onClose(); super.onClose();
} }
// 添加重试方法 /// 初始化控制器
void retryConnection() { /// 设置事件监听并加载版本信息
errorMessage.value = ''; Future<void> _initializeController() async {
_getCurrentVersion(); _listenToOtaEvents();
} await _loadVersionInfo();
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(),
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red[600],
colorText: Colors.white,
icon: const Icon(
Icons.error_outline,
color: Colors.white,
),
);
}
} }
Future<void> _getCurrentVersion() async { /// 加载版本信息
_listenToEvents(); /// 获取当前设备版本和最新版本信息,检查是否有更新
Future<void> _loadVersionInfo() async {
try { try {
isFetchingDeviceInfo.value = true; isFetchingDeviceInfo.value = true;
errorMessage.value = ''; 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('.');
Logger().d("firstPart${firstPart},secondPart=${secondPart}");
// 判断两部分是否一致
if (firstPart != secondPart) {
throw "固件版本格式错误:${deviceInfo.firmwareVersion}";
}
// 正常流程 // 获取当前版本信息
currentVersion.value = firstPart; final currentInfo = await _versionManager.getCurrentVersionInfo();
Logger().d('getequipmentinfo,id: ${deviceInfo.firmwareProductId}'); currentVersion.value = currentInfo['currentVersion'];
// 获取蓝牙地址
final connectedDevices = await ClassicBluetooth.getConnectedDevices(); final connectedDevices = await ClassicBluetooth.getConnectedDevices();
if (connectedDevices.isNotEmpty) { if (connectedDevices.isNotEmpty) {
_address = connectedDevices.first.address; _address = connectedDevices.first.address;
} }
DBProduct? product = AppConfig.getproduct("DeapSound"); // 获取最新版本信息
final latestInfo = await _versionManager.getLatestVersionInfo();
// // API请求 latestVersion.value = latestInfo['latestVersion'];
// final response = await Api.getproduct({ _updatePackageAddress = latestInfo['updatePackageAddress'];
// "id": deviceInfo.firmwareProductId,
// });
// // 处理API响应
// if (response['product'] == null) {
// throw const FormatException('API 响应格式错误');
// }
// final resp = OtaConfigResp.fromJson(response);
// Logger().d('getequipmentinfo,resp.systemversion: ${resp.systemversion}');
latestVersion.value = product?.version ?? "";
// updatedescription.value = resp.updatedescription!;
updatepackageaddress = product!.updatepackageaddress!;
// 版本检查 // 设置更新描述信息
bool hasUpdate = _isNewVersionAvailable(product.version!); updatedescription.value =
latestInfo['updateDescription'] ?? '本次更新包含性能优化和错误修复。';
isUpdateAvailable.value = hasUpdate; // 检查是否有更新
} catch (e) { isUpdateAvailable.value = _versionManager.isNewVersionAvailable(
// 统一错误处理 currentVersion.value,
errorMessage.value = e.toString(); latestVersion.value,
Get.snackbar(
'错误',
e.toString(),
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red[600],
colorText: Colors.white,
icon: const Icon(
Icons.error_outline,
color: Colors.white,
),
); );
// 重要:重新抛出异常以便上层处理(可选) } catch (e) {
_handleError('获取版本信息失败', e.toString());
} finally { } finally {
isFetchingDeviceInfo.value = false; isFetchingDeviceInfo.value = false;
} }
} }
// 版本比较方法优化 - 支持升级和降级 /// 检查更新
bool _isNewVersionAvailable(String latestVersion) { /// 显示更新确认对话框或错误提示
try {
List<int> currentParts = currentVersion.value
.split('.')
.map((e) => int.parse(e.trim()))
.toList();
List<int> latestParts =
latestVersion.split('.').map((e) => int.parse(e.trim())).toList();
// 确保两个版本号长度一致,短的用0补齐
int maxLength = currentParts.length > latestParts.length
? currentParts.length
: latestParts.length;
while (currentParts.length < maxLength) {
currentParts.add(0);
}
while (latestParts.length < maxLength) {
latestParts.add(0);
}
// 比较每一段,只要有不同就返回true(支持升级和降级)
for (int i = 0; i < maxLength; i++) {
if (latestParts[i] != currentParts[i]) {
return true; // 版本不同,允许更新(升级或降级)
}
}
return false; // 版本完全相同,无需更新
} catch (e) {
Logger().d('版本比较错误: $e');
return false;
}
}
// 版本更新
Future<void> checkUpdate() async { Future<void> checkUpdate() async {
try { try {
if (latestVersion.value != "") { if (latestVersion.value.isNotEmpty) {
_showUpdateDialog(latestVersion.value); _uiManager.showUpdateDialog(latestVersion.value, _startUpdateProcess);
} else { } else {
Get.snackbar( _uiManager.showErrorSnackbar('错误', '获取最新版本信息失败',
'错误', backgroundColor: Colors.orange[800]);
'获取最新版本信息失败',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange[800],
colorText: Colors.white,
icon: const Icon(
Icons.error_outline,
color: Colors.white,
),
);
} }
} catch (e) { } catch (e) {
Get.snackbar( _uiManager.showErrorSnackbar('错误', '获取版本信息失败',
'错误', backgroundColor: Colors.orange[800]);
'获取版本信息失败',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange[800],
colorText: Colors.white,
icon: const Icon(
Icons.error_outline,
color: Colors.white,
),
);
} }
} }
void _showUpdateDialog(String latest) async { /// 开始更新流程的公共方法
Logger().d('显示更新对话框'); /// 供视图调用的更新入口点
Get.dialog( Future<void> startUpdate() async {
AlertDialog( await _startUpdateProcess();
title: Text('发现新版本 $latest'),
content: Text('是否立即更新?'),
actions: [
TextButton(
onPressed: () async {
Logger().d('取消按钮被点击');
Get.back();
Future.delayed(Duration(milliseconds: 300));
},
child: Text('取消'),
),
TextButton(
onPressed: () async {
Logger().d('更新按钮被点击');
Get.back();
Future.delayed(Duration(milliseconds: 300));
// 检查设备电量
if (!_checkBatteryLevel()) {
return; // 电量不足,不执行升级
}
startDownload();
},
child: Text('更新'),
),
],
),
);
} }
// 修改后的startDownload方法 /// 检查设备状态后开始下载和升级
Future<void> startDownload() async { Future<void> _startUpdateProcess() async {
await _bleManager.setEnterOta(true); // 检查设备电量
await Future.delayed(const Duration(seconds: 2)); if (!_deviceChecker.checkBatteryLevel()) {
jlota.init(); return;
}
isDownloading.value = true;
showDetailedStatus.value = true;
detailedStatus.value = '准备下载固件包...';
int lastIndex = updatepackageaddress.lastIndexOf('/');
String fileName = updatepackageaddress.substring(lastIndex + 1);
String directoryName = "ota_upgrades";
final dio = Dio(BaseOptions( await _startDownload();
connectTimeout: const Duration(seconds: 5), }
receiveTimeout: Duration.zero,
sendTimeout: const Duration(seconds: 5),
headers: {'Accept-Encoding': 'gzip'},
));
type.value = DOWNLOADING_UP_FILES; /// 开始下载固件
/// 初始化OTA环境并下载固件包
Future<void> _startDownload() async {
try { try {
detailedStatus.value = '正在获取下载目录...'; await _bleManager.setEnterOta(true);
final Directory? downloadsDir = await getApplicationSupportDirectory(); await Future.delayed(const Duration(seconds: 2));
if (downloadsDir == null) { _jlota.init();
throw FileSystemException('无法访问下载目录');
} isDownloading.value = true;
showDetailedStatus.value = true;
final targetDir = type.value = OtaStatusManager.DOWNLOADING_UP_FILES;
Directory('${downloadsDir.path}/Download/$directoryName');
if (!await targetDir.exists()) { final filePath = await _downloadManager.downloadFirmware(
detailedStatus.value = '创建下载目录...'; downloadUrl: _updatePackageAddress,
await targetDir.create(recursive: true); onProgress: (progress) {
} downloadProgress.value = progress;
},
final savePath = '${targetDir.path}/$fileName'; onStatusUpdate: (status) {
detailedStatus.value = status;
// 新增:检查同名文件逻辑
detailedStatus.value = '检查本地文件...';
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',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red[600],
colorText: Colors.white,
icon: Icon(
Icons.error_outline,
color: Colors.white,
),
);
return;
}
if (existingSameFile) {
//Get.snackbar('文件已存在', '跳过下载,直接更新');
detailedStatus.value = '文件已存在,准备升级...';
startUpgrading(savePath);
return;
}
// 新增:删除其他文件逻辑
detailedStatus.value = '清理旧文件...';
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',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red[600],
colorText: Colors.white,
icon: Icon(
Icons.error_outline,
color: Colors.white,
),
);
//isUpgrade.value = false;
return;
}
Logger().d('文件保存路径: $savePath');
detailedStatus.value = '开始下载固件包...';
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;
detailedStatus.value =
'正在下载固件包... ${(received / total * 100).toStringAsFixed(1)}%';
}
}, },
); );
File file = File(savePath);
if (await file.exists()) {
// Get.snackbar('下载完成', '文件已保存至: $savePath');
detailedStatus.value = '下载完成,准备升级...';
} else {
Get.snackbar(
'错误',
'文件未找到,请检查写入权限',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange[600],
colorText: Colors.white,
icon: Icon(
Icons.error_outline,
color: Colors.white,
),
);
detailedStatus.value = '下载失败';
showDetailedStatus.value = false;
return;
}
isDownloading.value = false; isDownloading.value = false;
startUpgrading(savePath); await _startUpgrading(filePath);
return;
} on DioException catch (e) {
Get.snackbar(
'下载失败',
'错误: ${e.message}',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange[600],
colorText: Colors.white,
icon: Icon(
Icons.error_outline,
color: Colors.white,
),
);
} on FileSystemException catch (e) {
Get.snackbar(
'存储失败',
'无法写入文件: ${e.message}',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange[600],
colorText: Colors.white,
icon: Icon(
Icons.error_outline,
color: Colors.white,
),
);
} catch (e) { } catch (e) {
isDownloading.value = false; isDownloading.value = false;
detailedStatus.value = '下载失败,请检查网络连接'; detailedStatus.value = '下载失败,请检查网络连接';
showDetailedStatus.value = false; showDetailedStatus.value = false;
Get.snackbar( _handleError('下载失败', e.toString());
'下载失败',
'请检查网络连接: $e',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange[600],
colorText: Colors.white,
icon: Icon(
Icons.error_outline,
color: Colors.white,
),
);
Logger().d('下载失败: $e'); Logger().d('下载失败: $e');
} }
} }
// 开始升级 /// 开始升级
void startUpgrading(String path) async { /// [filePath] 固件文件路径
/// 启动OTA升级过程
Future<void> _startUpgrading(String filePath) async {
detailedStatus.value = '准备设备连接...'; detailedStatus.value = '准备设备连接...';
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
detailedStatus.value = '正在连接设备...'; detailedStatus.value = '正在连接设备...';
jlota.startUpgrading(address: _address, filePath: path); _jlota.startUpgrading(address: _address, filePath: filePath);
return;
// isUpgrade.value = false;
} }
// 停止升级 /// 停止升级
void stopUpgrading() async { /// 关闭OTA连接
jlota.onOtaClose(); void stopUpgrading() {
return; _jlota.onOtaClose();
// isUpgrade.value = false;
} }
// 检查设备电量是否满足升级条件 /// 重试连接
bool _checkBatteryLevel() { /// 清除错误信息并重新加载版本信息
try { void retryConnection() {
final deviceInfo = _bleManager.deviceInfo.value; errorMessage.value = '';
_loadVersionInfo();
if (deviceInfo == null) { }
Get.snackbar(
'电量检查失败',
'无法获取设备信息,请检查设备连接',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange[600],
colorText: Colors.white,
icon: Icon(
Icons.warning_amber,
color: Colors.white,
),
);
return false;
}
final leftBattery = deviceInfo.leftEarBattery; /// 获取状态描述
final rightBattery = deviceInfo.rightEarBattery; /// [type] 状态类型
/// 返回对应的状态描述文本
// 检查左右耳电量是否都大于30% String getStatusMessage(int type) {
if (leftBattery <= 30 || rightBattery <= 30) { return OtaStatusManager.getStatusMessage(type);
String message = '设备电量不足,无法进行固件升级\n'; }
message += '升级要求:左右耳电量均需大于30%\n';
message += '当前电量:左耳 ${leftBattery}%,右耳 ${rightBattery}%\n';
message += '请充电后再试';
Get.dialog(
AlertDialog(
title: Text(
'电量不足',
style: TextStyle(
color: Colors.orange[700],
fontWeight: FontWeight.w600,
),
),
content: Text(
message,
style: TextStyle(
color: Colors.orange[600],
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
style: TextButton.styleFrom(
foregroundColor: Colors.orange[700],
),
child: const Text('确定'),
),
],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: Colors.orange.withValues(alpha: 0.3),
width: 1,
),
),
backgroundColor: Colors.orange[50],
),
);
return false;
}
return true; /// 监听OTA事件
} catch (e) { /// 设置OTA事件流的监听器
Get.snackbar( void _listenToOtaEvents() {
'电量检查失败', _jlota.eventStream.listen((event) {
'检查设备电量时发生错误:$e', _handleOtaEvent(event);
snackPosition: SnackPosition.BOTTOM, });
backgroundColor: Colors.orange[600],
colorText: Colors.white,
icon: const Icon(
Icons.warning_amber,
color: Colors.white,
),
);
return false;
}
} }
void _listenToEvents() { /// 处理OTA事件
Ota.instance.eventStream.listen((event) { /// [event] OTA事件对象
if (event.event == 'onProgress' && /// 根据不同的事件类型执行相应的处理逻辑
event.type != null && void _handleOtaEvent(dynamic event) {
event.progress != null) { switch (event.event) {
progress.value = event.progress!; case 'onProgress':
type.value = event.type!; if (event.type != null && event.progress != null) {
isUpgrade.value = true; // 开始升级 progress.value = event.progress!;
detailedStatus.value = type.value = event.type!;
'升级进度: ${(event.progress! * 100).toStringAsFixed(1)}%'; isUpgrade.value = true;
} detailedStatus.value =
if (event.event == 'onStartOTA') { '升级进度: ${(event.progress! * 100).toStringAsFixed(1)}%';
type.value = START_UPGRADING; }
isUpgrade.value = true; // 开始升级 break;
case 'onStartOTA':
type.value = OtaStatusManager.START_UPGRADING;
isUpgrade.value = true;
detailedStatus.value = 'OTA升级已开始...'; detailedStatus.value = 'OTA升级已开始...';
} break;
if (event.event == 'onStopOTA') { case 'onStopOTA':
type.value = UPGRADE_SUCCESS; _handleUpgradeSuccess();
isUpgrade.value = false; break;
detailedStatus.value = '升级成功完成'; case 'onError':
showDetailedStatus.value = false; _handleUpgradeError(event.code);
break;
// 升级成功,更新版本信息显示 case 'NOCONNECTION':
currentVersion.value = latestVersion.value; _handleConnectionLost();
isUpdateAvailable.value = false; break;
case 'CONNECTION_SUCCESS':
// 显示升级成功对话框,引导用户重新配对 _handleConnectionSuccess();
Get.dialog( break;
AlertDialog( }
title: Row( }
children: [
Icon( /// 处理升级成功
Icons.check_circle, /// 更新状态并显示成功对话框
color: Colors.green[600], void _handleUpgradeSuccess() {
size: 28, type.value = OtaStatusManager.UPGRADE_SUCCESS;
), isUpgrade.value = false;
SizedBox(width: 12.w), detailedStatus.value = '升级成功完成';
Text( showDetailedStatus.value = false;
'升级成功',
style: TextStyle( // 更新版本信息
color: Colors.green[700], currentVersion.value = latestVersion.value;
fontWeight: FontWeight.w600, isUpdateAvailable.value = false;
),
), // 显示成功对话框
], _uiManager.showUpgradeSuccessDialog();
), }
content: Text(
'固件升级已完成!\n\n设备需要重新配对才能正常使用,现在跳转到配对页面吗?', /// 处理升级错误
style: TextStyle( /// [errorCode] 错误代码
fontSize: 16, /// 更新状态并显示错误提示
height: 1.4, void _handleUpgradeError(dynamic errorCode) {
), type.value = OtaStatusManager.UPGRADE_FAILED;
), isUpgrade.value = false;
actions: [ detailedStatus.value = '升级失败,错误码:$errorCode';
TextButton( showDetailedStatus.value = false;
onPressed: () { _uiManager.showErrorSnackbar(
Get.back(); // 关闭对话框 '升级失败',
}, '请检查设备连接或升级包,错误码:$errorCode',
child: Text( );
'稍后配对', }
style: TextStyle(color: Colors.grey[600]),
), /// 处理连接断开
), /// 更新连接状态
ElevatedButton( void _handleConnectionLost() {
onPressed: () { type.value = OtaStatusManager.CONNECTION_DISCONNECTION;
Get.back(); // 关闭对话框 isUpgrade.value = false;
Get.toNamed(Routes.pairing); detailedStatus.value = '设备连接断开';
}, showDetailedStatus.value = false;
style: ElevatedButton.styleFrom( }
backgroundColor: Colors.green[600],
foregroundColor: Colors.white, /// 处理连接成功
), /// 更新连接状态
child: Text('立即配对'), void _handleConnectionSuccess() {
), type.value = OtaStatusManager.CONNECTION_SUCCESS;
], isUpgrade.value = true;
shape: RoundedRectangleBorder( detailedStatus.value = '设备连接成功';
borderRadius: BorderRadius.circular(12), }
),
), /// 统一错误处理
barrierDismissible: false, // 防止用户意外关闭 /// [title] 错误标题
); /// [message] 错误信息
} /// 设置错误状态并显示错误提示
if (event.event == 'onError') { void _handleError(String title, String message) {
type.value = UPGRADE_FAILED; errorMessage.value = message;
isUpgrade.value = false; _uiManager.showErrorSnackbar(title, message);
detailedStatus.value = '升级失败,错误码:${event.code}';
showDetailedStatus.value = false;
Get.snackbar(
'升级失败',
'请检查设备连接或升级包,错误码:${event.code}',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red[600],
colorText: Colors.white,
icon: Icon(
Icons.error_outline,
color: Colors.white,
),
);
}
if (event.event == 'NOCONNECTION') {
type.value = CONNECTION_DISCONNECTION;
isUpgrade.value = false;
detailedStatus.value = '设备连接断开';
showDetailedStatus.value = false;
}
if (event.event == 'CONNECTION_SUCCESS') {
type.value = CONNECTION_SUCCESS;
isUpgrade.value = true; // 开始升级
detailedStatus.value = '设备连接成功';
}
});
} }
} }

99
lib/modules/ota_upgrade/services/device_checker.dart

@ -0,0 +1,99 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../../data/services/ble_manager.dart';
/// 设备检查服务
/// 负责检查设备状态,如电量检查等
class DeviceChecker {
final BleManager _bleManager = Get.find<BleManager>();
/// 检查设备电量是否满足升级条件
/// 返回true表示电量充足,可以进行升级
/// 返回false表示电量不足,不能进行升级
bool checkBatteryLevel() {
try {
final deviceInfo = _bleManager.deviceInfo.value;
if (deviceInfo == null) {
_showBatteryCheckFailedSnackbar('无法获取设备信息,请检查设备连接');
return false;
}
final leftBattery = deviceInfo.leftEarBattery;
final rightBattery = deviceInfo.rightEarBattery;
// 检查左右耳电量是否都大于30%
if (leftBattery <= 30 || rightBattery <= 30) {
_showLowBatteryDialog(leftBattery, rightBattery);
return false;
}
return true;
} catch (e) {
_showBatteryCheckFailedSnackbar('检查设备电量时发生错误:$e');
return false;
}
}
/// 显示电量不足对话框
/// [leftBattery] 左耳电量
/// [rightBattery] 右耳电量
void _showLowBatteryDialog(int leftBattery, int rightBattery) {
String message = '设备电量不足,无法进行固件升级\n';
message += '升级要求:左右耳电量均需大于30%\n';
message += '当前电量:左耳 ${leftBattery}%,右耳 ${rightBattery}%\n';
message += '请充电后再试';
Get.dialog(
AlertDialog(
title: Text(
'电量不足',
style: TextStyle(
color: Colors.orange[700],
fontWeight: FontWeight.w600,
),
),
content: Text(
message,
style: TextStyle(
color: Colors.orange[600],
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
style: TextButton.styleFrom(
foregroundColor: Colors.orange[700],
),
child: const Text('确定'),
),
],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: Colors.orange.withValues(alpha: 0.3),
width: 1,
),
),
backgroundColor: Colors.orange[50],
),
);
}
/// 显示电量检查失败提示
/// [message] 错误信息
void _showBatteryCheckFailedSnackbar(String message) {
Get.snackbar(
'电量检查失败',
message,
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange[600],
colorText: Colors.white,
icon: const Icon(
Icons.warning_amber,
color: Colors.white,
),
);
}
}

153
lib/modules/ota_upgrade/services/download_manager.dart

@ -0,0 +1,153 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
import 'package:logger/web.dart';
/// 文件下载管理器
/// 负责处理固件包的下载、文件管理等功能
class DownloadManager {
late final Dio _dio;
/// 构造函数
/// 初始化Dio实例并配置SSL证书处理
DownloadManager() {
_dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 30), // 增加连接超时时间
receiveTimeout: Duration.zero,
sendTimeout: const Duration(seconds: 30), // 增加发送超时时间
headers: {'Accept-Encoding': 'gzip'},
));
// 配置SSL证书验证
(_dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
final client = HttpClient();
// 处理SSL证书验证
client.badCertificateCallback =
(X509Certificate cert, String host, int port) {
// 在生产环境中,应该进行适当的证书验证
// 这里为了解决证书过期问题,暂时跳过验证
// 建议:在生产环境中实现更安全的证书验证逻辑
Logger().w('SSL证书验证跳过: $host:$port');
return true;
};
return client;
};
}
/// 下载固件包
/// [downloadUrl] 下载地址
/// [onProgress] 下载进度回调
/// [onStatusUpdate] 状态更新回调
/// 返回下载文件的本地路径
/// 抛出异常如果下载失败
Future<String> downloadFirmware({
required String downloadUrl,
required Function(double) onProgress,
required Function(String) onStatusUpdate,
}) async {
onStatusUpdate('准备下载固件包...');
final fileName = downloadUrl.substring(downloadUrl.lastIndexOf('/') + 1);
const directoryName = "ota_upgrades";
// 获取下载目录
onStatusUpdate('正在获取下载目录...');
final Directory? downloadsDir = await getApplicationSupportDirectory();
if (downloadsDir == null) {
throw FileSystemException('无法访问下载目录');
}
final targetDir = Directory('${downloadsDir.path}/Download/$directoryName');
if (!await targetDir.exists()) {
onStatusUpdate('创建下载目录...');
await targetDir.create(recursive: true);
}
final savePath = '${targetDir.path}/$fileName';
// 检查是否已存在同名文件
onStatusUpdate('检查本地文件...');
if (await _checkExistingFile(targetDir, fileName)) {
onStatusUpdate('文件已存在,准备升级...');
return savePath;
}
// 清理旧文件
onStatusUpdate('清理旧文件...');
await _cleanOldFiles(targetDir);
// 开始下载
onStatusUpdate('开始下载固件包...');
Logger().d('文件保存路径: $savePath');
final options = Options(
receiveTimeout: Duration.zero,
followRedirects: true,
maxRedirects: 5,
);
await _dio.download(
downloadUrl,
savePath,
options: options,
onReceiveProgress: (received, total) {
if (total != -1) {
final progress = received / total;
onProgress(progress);
onStatusUpdate('正在下载固件包... ${(progress * 100).toStringAsFixed(1)}%');
}
},
);
// 验证文件是否下载成功
final file = File(savePath);
if (!await file.exists()) {
throw FileSystemException('文件下载失败,请检查写入权限');
}
onStatusUpdate('下载完成,准备升级...');
return savePath;
}
/// 检查是否存在同名文件
/// [targetDir] 目标目录
/// [fileName] 文件名
/// 返回true表示文件已存在
Future<bool> _checkExistingFile(Directory targetDir, String fileName) async {
try {
final files = await targetDir.list().toList();
for (var entity in files) {
if (await FileSystemEntity.isFile(entity.path)) {
final name = basename(entity.path);
if (name == fileName) {
return true;
}
}
}
return false;
} catch (e) {
Logger().d('检查本地文件失败: $e');
return false;
}
}
/// 清理旧文件
/// [targetDir] 目标目录
/// 抛出异常如果清理失败
Future<void> _cleanOldFiles(Directory targetDir) async {
try {
final files = await targetDir.list().toList();
for (var entity in files) {
if (await FileSystemEntity.isFile(entity.path)) {
await entity.delete();
}
}
} catch (e) {
throw Exception('删除旧文件失败: $e');
}
}
}

43
lib/modules/ota_upgrade/services/ota_status_manager.dart

@ -0,0 +1,43 @@
import 'package:get/get.dart';
/// OTA升级状态管理器
/// 负责管理OTA升级过程中的各种状态常量和状态描述
class OtaStatusManager {
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; // 下载升级文件
/// 获取状态描述信息
/// [type] 状态类型
/// 返回对应的状态描述文本
static String getStatusMessage(int type) {
switch (type) {
case NOT_STARTED:
return "未开始".tr;
case VALIDATION_FILE:
return "验证文件中".tr;
case ONUPGRADE:
return "升级中".tr;
case START_UPGRADING:
return "开始升级".tr;
case UPGRADE_SUCCESS:
return "升级成功".tr;
case UPGRADE_FAILED:
return "升级失败".tr;
case CONNECTION_DISCONNECTION:
return "连接断开".tr;
case CONNECTION_SUCCESS:
return "连接成功".tr;
case DOWNLOADING_UP_FILES:
return "下载升级文件".tr;
default:
return "未知状态".tr;
}
}
}

117
lib/modules/ota_upgrade/services/ui_manager.dart

@ -0,0 +1,117 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:logger/web.dart';
import '/routes/app_routes.dart';
/// UI交互管理器
/// 负责处理所有与用户界面交互相关的功能
class UiManager {
/// 显示更新确认对话框
/// [latestVersion] 最新版本号
/// [onConfirm] 确认更新的回调函数
void showUpdateDialog(String latestVersion, VoidCallback onConfirm) {
Logger().d('显示更新对话框');
Get.dialog(
AlertDialog(
title: Text('发现新版本 $latestVersion'),
content: const Text('是否立即更新?'),
actions: [
TextButton(
onPressed: () {
Logger().d('取消按钮被点击');
Get.back();
},
child: const Text('取消'),
),
TextButton(
onPressed: () {
Logger().d('更新按钮被点击');
Get.back();
Future.delayed(const Duration(milliseconds: 300));
onConfirm();
},
child: const Text('更新'),
),
],
),
);
}
/// 显示升级成功对话框
/// 引导用户进行设备重新配对
void showUpgradeSuccessDialog() {
Get.dialog(
AlertDialog(
title: Row(
children: [
Icon(
Icons.check_circle,
color: Colors.green[600],
size: 28,
),
SizedBox(width: 12.w),
Text(
'升级成功',
style: TextStyle(
color: Colors.green[700],
fontWeight: FontWeight.w600,
),
),
],
),
content: const Text(
'固件升级已完成!\n\n设备需要重新配对才能正常使用,现在跳转到配对页面吗?',
style: TextStyle(
fontSize: 16,
height: 1.4,
),
),
actions: [
TextButton(
onPressed: () {
Get.back();
},
child: Text(
'稍后配对',
style: TextStyle(color: Colors.grey[600]),
),
),
ElevatedButton(
onPressed: () {
Get.back();
Get.toNamed(Routes.pairing);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green[600],
foregroundColor: Colors.white,
),
child: const Text('立即配对'),
),
],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
barrierDismissible: false,
);
}
/// 显示错误提示
/// [title] 错误标题
/// [message] 错误信息
/// [backgroundColor] 背景颜色,可选
void showErrorSnackbar(String title, String message, {Color? backgroundColor}) {
Get.snackbar(
title,
message,
snackPosition: SnackPosition.BOTTOM,
backgroundColor: backgroundColor ?? Colors.red[600],
colorText: Colors.white,
icon: const Icon(
Icons.error_outline,
color: Colors.white,
),
);
}
}

114
lib/modules/ota_upgrade/services/version_manager.dart

@ -0,0 +1,114 @@
import 'package:logger/web.dart';
import '../../../data/services/ble_manager.dart';
import '../../../data/models/appconfig.dart';
import '../../../data/models/appconfig_model.dart';
import 'package:get/get.dart';
/// 版本管理服务
/// 负责处理设备版本信息获取、版本比较等功能
class VersionManager {
final BleManager _bleManager = Get.find<BleManager>();
final int maxRetryAttempts = 3;
int currentRetryCount = 0;
/// 获取当前设备版本信息
/// 返回包含当前版本、产品ID和设备信息的Map
/// 抛出异常如果获取失败
Future<Map<String, dynamic>> getCurrentVersionInfo() async {
DeviceInfo? deviceInfo = _bleManager.deviceInfo.value;
// 重试机制获取设备信息
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 "获取设备信息失败,请检查连接";
}
// 验证固件版本格式
final firmwareParts = deviceInfo.firmwareVersion.split('.');
final firstPart = firmwareParts.sublist(0, 3).join('.');
final secondPart = firmwareParts.sublist(3).join('.');
Logger().d("firstPart: $firstPart, secondPart: $secondPart");
if (firstPart != secondPart) {
throw "固件版本格式错误:${deviceInfo.firmwareVersion}";
}
return {
'currentVersion': firstPart,
'productId': deviceInfo.firmwareProductId,
'deviceInfo': deviceInfo,
};
}
/// 获取最新版本信息
/// 返回包含最新版本、更新描述和更新包地址的Map
/// 抛出异常如果获取失败
Future<Map<String, dynamic>> getLatestVersionInfo() async {
DBProduct? product = AppConfig.getproduct("DeapSound");
if (product == null) {
throw "无法获取产品信息";
}
return {
'latestVersion': product.version ?? "",
'updateDescription': product.updatedescription ?? "",
'updatePackageAddress': product.updatepackageaddress ?? "",
};
}
/// 比较版本号,判断是否有新版本可用
/// [currentVersion] 当前版本号
/// [latestVersion] 最新版本号
/// 返回true表示有新版本可用(支持升级和降级)
bool isNewVersionAvailable(String currentVersion, String latestVersion) {
try {
List<int> currentParts = currentVersion
.split('.')
.map((e) => int.parse(e.trim()))
.toList();
List<int> latestParts =
latestVersion.split('.').map((e) => int.parse(e.trim())).toList();
// 确保两个版本号长度一致,短的用0补齐
int maxLength = currentParts.length > latestParts.length
? currentParts.length
: latestParts.length;
while (currentParts.length < maxLength) {
currentParts.add(0);
}
while (latestParts.length < maxLength) {
latestParts.add(0);
}
// 比较每一段,只要有不同就返回true(支持升级和降级)
for (int i = 0; i < maxLength; i++) {
if (latestParts[i] != currentParts[i]) {
return true;
}
}
return false;
} catch (e) {
Logger().d('版本比较错误: $e');
return false;
}
}
}
Loading…
Cancel
Save