Browse Source

ota升级优化版

weicu
fdp 1 year ago
parent
commit
07a214af2e
  1. 110
      lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
  2. 98
      lib/modules/ota_upgrade/views/ota_upgrade_view.dart
  3. 46
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  4. 116
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/OTAManager.kt
  5. 282
      local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt
  6. 7
      local_plugins/ota/android/src/main/kotlin/com/example/ota/tool/ota/OTAManager.kt
  7. 145
      local_plugins/ota/lib/ota.dart
  8. 20
      local_plugins/ota/lib/ota_method_channel.dart
  9. 12
      local_plugins/ota/lib/ota_platform_interface.dart
  10. 2
      local_plugins/ota/pubspec.yaml

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

@ -11,18 +11,28 @@ import 'package:ota/ota.dart';
import '../../../data/services/ble_manager.dart'; import '../../../data/services/ble_manager.dart';
class OtaUpgradeController extends GetxController { 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 errorMessage = ''.obs;
final RxBool isFetchingDeviceInfo = false.obs; final RxBool isFetchingDeviceInfo = false.obs;
final Dio _dio = Dio();
final RxString latestVersion = ''.obs; final RxString latestVersion = ''.obs;
final RxString currentVersion = ''.obs; final RxString currentVersion = ''.obs;
String updatepackageaddress = ''; String updatepackageaddress = '';
final RxDouble downloadProgress = 0.0.obs; final RxDouble downloadProgress = 0.0.obs;
final RxBool isUpdating = false.obs;
final isUpdateAvailable = true.obs;
final isUpdateAvailable = true.obs;
//int currentProgress = 0; // 当前进度值,范围0-100
final RxDouble progress = 0.0.obs; final RxDouble progress = 0.0.obs;
var jlota = Ota(); final jlota = Ota.instance;
final RxBool isDownloading = false.obs;
// 蓝牙服务 // 蓝牙服务
final _bleManager = Get.find<BleManager>(); final _bleManager = Get.find<BleManager>();
@override @override
@ -31,30 +41,57 @@ class OtaUpgradeController extends GetxController {
_getCurrentVersion(); _getCurrentVersion();
} }
@override
void onClose() {
jlota.onOtaClose();
super.onClose();
}
// 添加重试方法 // 添加重试方法
void retryConnection() { void retryConnection() {
errorMessage.value = ''; errorMessage.value = '';
_getCurrentVersion(); _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 { Future<void> startUpdate() async {
try { try {
isUpdating.value = true;
checkUpdate(); checkUpdate();
// 实现更新逻辑... // 实现更新逻辑...
} catch (e) { } catch (e) {
errorMessage.value = e.toString(); errorMessage.value = e.toString();
Get.snackbar('更新失败', e.toString()); Get.snackbar('更新失败', e.toString());
} finally {
isUpdating.value = false;
} }
} }
Future<void> _getCurrentVersion() async { Future<void> _getCurrentVersion() async {
try { try {
jlota.getPlatformVersion();
// 添加获取设备信息的重试机制 // 添加获取设备信息的重试机制
DeviceInfo? deviceInfo = _bleManager.deviceInfo.value; DeviceInfo? deviceInfo = _bleManager.deviceInfo.value;
@ -158,12 +195,18 @@ class OtaUpgradeController extends GetxController {
content: Text('是否立即更新?'), content: Text('是否立即更新?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Get.back(), onPressed: () {
if (Get.isDialogOpen!) {
Get.back(); // 确保只关闭对话框
}
},
child: Text('取消'), child: Text('取消'),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
Get.back(); if (Get.isDialogOpen!) {
Get.back(); // 确保只关闭对话框
}
startDownload(); startDownload();
}, },
child: Text('更新'), child: Text('更新'),
@ -175,7 +218,7 @@ class OtaUpgradeController extends GetxController {
// 修改后的startDownload方法 // 修改后的startDownload方法
Future<void> startDownload() async { Future<void> startDownload() async {
isUpdating.value = true; isDownloading.value = true;
int lastIndex = updatepackageaddress.lastIndexOf('/'); int lastIndex = updatepackageaddress.lastIndexOf('/');
String fileName = updatepackageaddress.substring(lastIndex + 1); String fileName = updatepackageaddress.substring(lastIndex + 1);
String directoryName = "ota_upgrades"; String directoryName = "ota_upgrades";
@ -187,8 +230,7 @@ class OtaUpgradeController extends GetxController {
headers: {'Accept-Encoding': 'gzip'}, headers: {'Accept-Encoding': 'gzip'},
)); ));
Get.snackbar('升级命令已发送', '开始下载...'); jlota.type.value = DOWNLOADING_UP_FILES;
try { try {
final Directory? downloadsDir = await getDownloadsDirectory(); final Directory? downloadsDir = await getDownloadsDirectory();
if (downloadsDir == null) { if (downloadsDir == null) {
@ -217,13 +259,13 @@ class OtaUpgradeController extends GetxController {
} }
} catch (e) { } catch (e) {
Get.snackbar('错误', '检查本地文件失败: $e'); Get.snackbar('错误', '检查本地文件失败: $e');
isUpdating.value = false;
return; return;
} }
if (existingSameFile) { if (existingSameFile) {
Get.snackbar('文件已存在', '跳过下载,直接更新'); Get.snackbar('文件已存在', '跳过下载,直接更新');
isUpdating.value = false;
_installApp(savePath); _installApp(savePath);
return; return;
} }
@ -238,7 +280,7 @@ class OtaUpgradeController extends GetxController {
} }
} catch (e) { } catch (e) {
Get.snackbar('错误', '删除旧文件失败: $e'); Get.snackbar('错误', '删除旧文件失败: $e');
isUpdating.value = false; //isUpgrade.value = false;
return; return;
} }
@ -249,59 +291,39 @@ class OtaUpgradeController extends GetxController {
followRedirects: true, followRedirects: true,
maxRedirects: 5, maxRedirects: 5,
); );
// 修改下载部分
DateTime lastUpdate = DateTime.now();
await dio.download( await dio.download(
updatepackageaddress, updatepackageaddress,
savePath, savePath,
options: options, options: options,
onReceiveProgress: (received, total) { onReceiveProgress: (received, total) {
final now = DateTime.now(); if (total != -1) {
if (now.difference(lastUpdate).inMilliseconds > 1000) { downloadProgress.value = received / total;
lastUpdate = now;
String progressMsg = total != -1
? '${(received / total * 100).toStringAsFixed(1)}%'
: '已下载:${(received / 1024 / 1024).toStringAsFixed(1)}MB';
Get.snackbar(
'升级进度',
progressMsg,
duration: const Duration(seconds: 1),
);
} }
}, },
); );
File file = File(savePath); File file = File(savePath);
if (await file.exists()) { if (await file.exists()) {
Get.snackbar('下载完成', '文件已保存至: $savePath'); Get.snackbar('下载完成', '文件已保存至: $savePath');
} else { } else {
Get.snackbar('错误', '文件未找到,请检查写入权限'); Get.snackbar('错误', '文件未找到,请检查写入权限');
} }
isUpdating.value = false; isDownloading.value = false;
_installApp(savePath); _installApp(savePath);
return;
} on DioException catch (e) { } on DioException catch (e) {
Get.snackbar('下载失败', '错误: ${e.message}'); Get.snackbar('下载失败', '错误: ${e.message}');
isUpdating.value = false;
} on FileSystemException catch (e) { } on FileSystemException catch (e) {
Get.snackbar('存储失败', '无法写入文件: ${e.message}'); Get.snackbar('存储失败', '无法写入文件: ${e.message}');
isUpdating.value = false;
} catch (e) { } catch (e) {
Get.snackbar('未知错误', e.toString()); Get.snackbar('未知错误', e.toString());
isUpdating.value = false;
} }
} }
// 开始升级 // 开始升级
void _installApp(String path) async { void _installApp(String path) async {
jlota.searchDevices(); jlota.searchDevices(path);
// jlota.startUpgrade(); return;
// JlOtaPlugin() // isUpgrade.value = false;
// final File file = File(path);
// if (await file.exists()) {
// // OpenResult result = await OpenFile.open(path);
// // if (result.type != ResultType.done) {
// // Get.snackbar('错误', '安装失败: ${result.message}');
// // }
// }
} }
} }

98
lib/modules/ota_upgrade/views/ota_upgrade_view.dart

@ -101,7 +101,7 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
fontSize: 18, fontSize: 18,
color: isDarkMode ? Colors.grey[300] : Colors.black)), color: isDarkMode ? Colors.grey[300] : Colors.black)),
SizedBox(width: 8), // 改为width SizedBox(width: 8), // 改为width
Obx(() => Text('v${controller.currentVersion}', Obx(() => Text('V ${controller.currentVersion}',
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
@ -117,11 +117,11 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
fontSize: 18, fontSize: 18,
color: isDarkMode ? Colors.grey[300] : Colors.black)), color: isDarkMode ? Colors.grey[300] : Colors.black)),
SizedBox(width: 8), // 改为width SizedBox(width: 8), // 改为width
Text('v${controller.latestVersion}', Obx(() => Text('V ${controller.latestVersion}',
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
color: isDarkMode ? Colors.green[300] : Colors.green)), color: isDarkMode ? Colors.blue[300] : Colors.blue))),
], ],
), ),
], ],
@ -227,7 +227,7 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
); );
Widget _buildUpdateButton(bool isDarkMode) { Widget _buildUpdateButton(bool isDarkMode) {
return controller.isUpdating.value return controller.jlota.isUpgrade.value
? _buildProgressIndicator(isDarkMode) ? _buildProgressIndicator(isDarkMode)
: Container( : Container(
width: double.infinity, width: double.infinity,
@ -262,7 +262,7 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
if (controller.isUpdateAvailable.value) ...[ if (controller.isUpdateAvailable.value) ...[
SizedBox(height: 8), SizedBox(height: 8),
Text( Text(
'建议连接WiFi并保持充电状态'.tr, '取出双耳机,并保证电量均为30%以上的状态'.tr,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600], color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
@ -274,61 +274,67 @@ class OtaUpgradeView extends GetView<OtaUpgradeController> {
); );
} }
Widget _buildProgressIndicator(bool isDarkMode) => Container( Widget _buildProgressIndicator(bool isDarkMode) {
padding: EdgeInsets.all(20), return Obx(() {
decoration: BoxDecoration( final progress = controller.jlota.progress.value;
color: final type = controller.jlota.type.value;
isDarkMode ? Colors.blue[900]!.withOpacity(0.2) : Colors.blue[50],
borderRadius: BorderRadius.circular(16), // // 处理升级失败状态:弹出 snackbar 并显示错误提示
), // if (type == OtaUpgradeController.UPGRADE_FAILED ||
// type == OtaUpgradeController.CONNECTION_DISCONNECTION) {
// // 使用延迟确保在 build 完成后执行
// Future.delayed(Duration.zero, () {
// Get.snackbar('升级失败', '重新连接设备后重试');
// });
// return Container(
// width: double.infinity,
// padding: const EdgeInsets.symmetric(vertical: 10),
// child: Text(
// '升级失败,请重新连接设备',
// style: TextStyle(
// fontSize: 18,
// fontWeight: FontWeight.bold,
// color: Colors.red[700],
// ),
// ),
// );
// }
// 正常升级状态
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 10),
child: Column( child: Column(
children: [ children: [
Text('正在下载更新...'.tr, Text(
controller.getStatusMessage(type),
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.blue[300] : Colors.blue[700])), color: isDarkMode ? Colors.blue[300] : Colors.blue[700],
SizedBox(height: 16),
//SizedBox(
//width: 240,
Container(
decoration: BoxDecoration(
border: Border.all(
color: isDarkMode ? Colors.blue[400]! : Colors.blue[300]!,
width: 1.5,
), ),
borderRadius: BorderRadius.circular(12),
),
child: LinearProgressIndicator(
value: controller.downloadProgress.value,
backgroundColor:
isDarkMode ? Colors.grey[800] : Colors.grey[200],
valueColor: AlwaysStoppedAnimation(
isDarkMode ? Colors.blue[300] : Colors.blue),
minHeight: 12,
borderRadius: BorderRadius.circular(10),
), ),
const SizedBox(height: 16),
LinearProgressIndicator(
value: progress / 100.0, // 转换为 0.0-1.0
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[200],
valueColor: AlwaysStoppedAnimation<Color>(
isDarkMode ? Colors.blue[300]! : Colors.blue,
), ),
SizedBox(height: 16),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.downloading,
color: isDarkMode ? Colors.blue[300] : Colors.blue,
size: 20,
), ),
SizedBox(width: 8), const SizedBox(height: 8),
Text( Text(
'${(controller.downloadProgress.value * 100).toStringAsFixed(1)}%', '${progress.toStringAsFixed(1)}%',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: color: isDarkMode ? Colors.blue[300] : Colors.blue[700],
isDarkMode ? Colors.blue[300] : Colors.blue[700])), ),
], ),
)
], ],
), ),
); );
});
}
} }

46
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -30,7 +30,6 @@ import com.jieli.jl_audio_decode.callback.OnStateCallback
import com.jieli.jl_audio_decode.opus.OpusManager import com.jieli.jl_audio_decode.opus.OpusManager
import com.jieli.jl_audio_decode.exceptions.OpusException import com.jieli.jl_audio_decode.exceptions.OpusException
import com.yunqiinnovation.ble_service.BleCompanionManager import com.yunqiinnovation.ble_service.BleCompanionManager
import com.yunqiinnovation.ble_service.OTAManager
import com.jieli.jl_bt_ota.model.BluetoothOTAConfigure; import com.jieli.jl_bt_ota.model.BluetoothOTAConfigure;
import com.jieli.jl_bt_ota.constant.ErrorCode; import com.jieli.jl_bt_ota.constant.ErrorCode;
import com.jieli.jl_bt_ota.constant.StateCode; import com.jieli.jl_bt_ota.constant.StateCode;
@ -89,7 +88,8 @@ object BleService {
private lateinit var scanHandler: Handler private lateinit var scanHandler: Handler
private val scanResults = ArrayList<ScanResult>() private val scanResults = ArrayList<ScanResult>()
private var isScanning = false private var isScanning = false
var isota = false // 是否是ota模式
var isEnterOta = false
// 连接状态 // 连接状态
val connectionState = MutableLiveData(BleConst.STATE_DISCONNECTED) val connectionState = MutableLiveData(BleConst.STATE_DISCONNECTED)
@ -354,9 +354,10 @@ object BleService {
return return
} }
Log.i(TAG, "检测到已配对设备,MAC: $pairedMac,尝试连接")
if(!isota) if(!isEnterOta)
{ {
Log.i(TAG, "检测到已配对设备,MAC: $pairedMac,尝试连接")
// 调用现有的 connect 方法 // 调用现有的 connect 方法
connect(pairedMac) connect(pairedMac)
} }
@ -430,44 +431,7 @@ object BleService {
audioChar = null audioChar = null
} }
} }
// fun startOTA() {
// try {
// otaManager?.startOTA(CustomUpdateCallback(context, null))
// Log.i(TAG, "OTA 升级已启动")
// } catch (e: IllegalStateException) {
// Log.e(TAG, "OTA 启动失败: ${e.message}")
// disconnectGatt()
// }
// }
// // 配置OTA
// fun configureOTA() {
// try {
// val firmwarePath =
// context.getExternalFilesDir("downloads/ota_upgrades")?.path + "/1.ufw"
// val file = File(firmwarePath)
// if (!file.exists()) {
// // throw IOException("Firmware file not found")
// Log.i(TAG, "Firmware file not found")
// }
// val bluetoothOption = BluetoothOTAConfigure().apply {
// priority = BluetoothOTAConfigure.PREFER_BLE
// isUseReconnect = true
// isUseAuthDevice = true
// mtu = 512
// isNeedChangeMtu = false // 确保MTU请求生效
// isUseJLServer = false
// firmwareFilePath = firmwarePath
// }
// otaManager?.configure(bluetoothOption) ?: run {
// Log.e(TAG, "OTA Manager未初始化")
// }
// } catch (e: Exception) {
// Log.e(TAG, "OTA配置失败: ${e.message}")
// // 处理错误
// }
// }
/** /**
* 更新连接状态并通知回调 * 更新连接状态并通知回调

116
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/OTAManager.kt

@ -1,116 +0,0 @@
package com.yunqiinnovation.ble_service
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothProfile
import android.content.Context
import com.jieli.jl_bt_ota.constant.ErrorCode;
import com.jieli.jl_bt_ota.constant.StateCode;
import com.jieli.jl_bt_ota.impl.BluetoothOTAManager;
import com.jieli.jl_bt_ota.interfaces.BtEventCallback;
import com.jieli.jl_bt_ota.interfaces.IActionCallback;
import com.jieli.jl_bt_ota.interfaces.IUpgradeCallback;
import com.jieli.jl_bt_ota.model.BluetoothOTAConfigure;
import com.jieli.jl_bt_ota.model.base.BaseError;
import com.jieli.jl_bt_ota.model.response.TargetInfoResponse;
import android.util.Log
import java.util.*
class CustomUpdateCallback(
val context: Context,
val device: BluetoothDevice?
) : IUpgradeCallback {
companion object {
private const val TAG = "JlOtaPlugin"
private const val CHANNEL_NAME = "com.yunqiinnovation.jl_ota"
private const val EVENT_CHANNEL_NAME = "com.yunqiinnovation.jl_ota/events"
}
override fun onStartOTA() {
Log.d(TAG, "onStartOTA开始升级")
// viewModel.otaStateMLD.value = OTAStart(device)
}
override fun onNeedReconnect(addr: String?, isNewReconnectWay: Boolean) {
Log.d(TAG, "onNeedReconnect需要重新链接")
}
override fun onProgress(type: Int, progress: Float) {
Log.d(TAG, "onProgress正在进展")
//viewModel.otaStateMLD.value = OTAWorking(device, type, progress)
}
override fun onStopOTA() {
Log.d(TAG, "onStartOTA停止升级")
}
override fun onCancelOTA() {
Log.d(TAG, "onCancelOTA取消升级")
}
override fun onError(error: BaseError?) {
val errorMessage = error?.message ?: "Unknown error"
Log.e(TAG, "升级失败: ${error?.message ?: "未知错误"}")
// 如果需要打印完整错误对象信息(假设BaseError有重写toString())
Log.d(TAG, "完整错误对象: ${error ?: "null"}")
}
}
class OTAManager(context: Context) : BluetoothOTAManager(context) {
// 定义回调接口
interface DataCallback {
fun onDataReceived(device: BluetoothDevice?, data: ByteArray?)
}
// 保存回调的实例
private var dataCallback: DataCallback? = null
// 设置回调的方法
fun setDataCallback(callback: DataCallback) {
this.dataCallback = callback
}
override fun getConnectedDevice(): BluetoothDevice? {
// 这里可以添加空实现的注释说明
Log.i(TAG, "ota数据发送getConnectedDevice")
return null
}
override fun getConnectedBluetoothGatt(): BluetoothGatt? {
// 这里可以添加空实现的注释说明
Log.i(TAG, "ota数据发送getConnectedBluetoothGatt")
return null
}
override fun connectBluetoothDevice(bluetoothDevice: BluetoothDevice?) {
// 这里可以添加空实现的注释说明
Log.i(TAG, "ota数据发送connectBluetoothDevice")
}
override fun disconnectBluetoothDevice(bluetoothDevice: BluetoothDevice?) {
// 这里可以添加空实现的注释说明
Log.i(TAG, "ota数据发送disconnectBluetoothDevice")
}
/** 数据发送方法(协议无关) */
override fun sendDataToDevice(
bluetoothDevice: BluetoothDevice?,
bytes: ByteArray?
): Boolean {
Log.i(TAG, "ota数据发送${bluetoothDevice},${bytes?.size}")
// 触发回调,将数据回传给调用者
dataCallback?.onDataReceived(bluetoothDevice, bytes)
// context.onReceiveDeviceData(device, data);
return true
}
}

282
local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt

@ -32,46 +32,114 @@ import android.os.Looper
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.RandomAccessFile import java.io.RandomAccessFile
class CustomUpdateCallback( import io.flutter.plugin.common.EventChannel
val device: BluetoothDevice? class CustomUpdateCallback(
val device: BluetoothDevice?,
private val streamSink: EventChannel.EventSink?
) : IUpgradeCallback { ) : IUpgradeCallback {
companion object { companion object {
private const val TAG = "JlOtaPlugin" private const val TAG = "JlOtaPlugin"
private const val CHANNEL_NAME = "com.yunqiinnovation.jl_ota" private const val CHANNEL_NAME = "com.example.ota"
private const val EVENT_CHANNEL_NAME = "com.yunqiinnovation.jl_ota/events" private const val EVENT_CHANNEL_NAME = "com.example.ota/events"
} }
override fun onStartOTA() { override fun onStartOTA() {
Log.d(TAG, "onStartOTA开始升级") Log.d(TAG, "onStartOTA开始升级")
// viewModel.otaStateMLD.value = OTAStart(device) if (streamSink != null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
streamSink.success(
mapOf(
"event" to "onStartOTA"
)
)
}
}
} }
override fun onNeedReconnect(addr: String?, isNewReconnectWay: Boolean) { override fun onNeedReconnect(addr: String?, isNewReconnectWay: Boolean) {
Log.d(TAG, "onNeedReconnect需要重新链接") Log.d(TAG, "onNeedReconnect需要重新链接")
if (streamSink != null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
streamSink.success(
mapOf(
"event" to "onNeedReconnect",
"addr" to addr,
"isNewReconnectWay" to isNewReconnectWay
)
)
}
}
} }
override fun onProgress(type: Int, progress: Float) { override fun onProgress(type: Int, progress: Float) {
Log.d(TAG, "onProgress正在进展") Log.d(TAG, "onProgress正在进展")
//viewModel.otaStateMLD.value = OTAWorking(device, type, progress) if (streamSink != null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
streamSink.success(
mapOf(
"event" to "onProgress",
"type" to type,
"progress" to progress
)
)
} else {
// Handler(Looper.getMainLooper()).post {
// streamSink.success(
// mapOf(
// "event" to "onDecodeStream"
// //"data" to data
// )
// )
// }
}
}
} }
override fun onStopOTA() { override fun onStopOTA() {
Log.d(TAG, "onStartOTA停止升级") Log.d(TAG, "onStartOTA停止升级")
if (streamSink != null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
streamSink.success(
mapOf(
"event" to "onStopOTA",
)
)
}
}
BleService.isEnterOta = false
} }
override fun onCancelOTA() { override fun onCancelOTA() {
Log.d(TAG, "onCancelOTA取消升级") Log.d(TAG, "onCancelOTA取消升级")
if (streamSink != null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
streamSink.success(
mapOf(
"event" to "onCancelOTA",
)
)
}
}
} }
override fun onError(error: BaseError?) { override fun onError(error: BaseError?) {
val errorMessage = error?.message ?: "Unknown error" val errorMessage = error?.message ?: "Unknown error"
Log.e(TAG, "升级失败: ${error?.message ?: "未知错误"}") Log.d(TAG, "升级失败: ${error?.message ?: "未知错误"}")
if (streamSink != null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
Log.d(TAG, "升级失败:error?.message ${error?.message}未知错误 error?.code${error?.code}")
streamSink.success(
mapOf(
"event" to "onError",
"code" to (error?.code ?: 255), // 确保值是正确的类型
"message" to (error?.message ?: "Unknown error") // 确保值是正确的类型
)
)
}
}
// 如果需要打印完整错误对象信息(假设BaseError有重写toString()) // 如果需要打印完整错误对象信息(假设BaseError有重写toString())
Log.d(TAG, "完整错误对象: ${error ?: "null"}") Log.d(TAG, "完整错误对象: ${error ?: "null"}")
} }
@ -83,90 +151,179 @@ class OtaPlugin : BleService.Callback, FlutterPlugin, MethodCallHandler {
/// ///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it /// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity /// when the Flutter Engine is detached from the Activity
private lateinit var channel : MethodChannel private lateinit var channel: MethodChannel
private lateinit var context: Context private lateinit var context: Context
private lateinit var device1: BluetoothDevice private lateinit var otaDevice: BluetoothDevice
private lateinit var bluetoothHelper : BluetoothHelper private lateinit var bluetoothHelper: BluetoothHelper
val otaConnectionMLD = MutableLiveData<DeviceConnection>()
val mandatoryUpgradeMLD = MutableLiveData<BluetoothDevice>() private lateinit var otaManager: OTAManager
private lateinit var otaManager : OTAManager private lateinit var filePath: String//升级文件路径
private var isDisconnect = false//是否主动断开
private var streamSink: EventChannel.EventSink? = null
private lateinit var eventChannel: EventChannel
// 是否已注册BLE回调
private var isRegisteredToBle = false
// 添加主线程Handler用于确保在主线程回调
private val mainHandler = Handler(Looper.getMainLooper())
companion object {
private const val TAG = "OtaPlugin"
private const val CHANNEL_NAME = "com.example.ota"
private const val EVENT_CHANNEL_NAME = "com.example.ota/events"
}
// 在主线程上安全发送事件
private fun sendEventOnMainThread(event: Map<String, Any?>) {
if (streamSink == null) return
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接发送
streamSink?.success(event)
} else {
// 否则切换到主线程
mainHandler.post {
streamSink?.success(event)
}
}
}
// 在主线程上安全返回结果
private fun sendResultOnMainThread(result: Result, value: Any?) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接返回
result.success(value)
} else {
// 否则切换到主线程
mainHandler.post {
result.success(value)
}
}
}
// 在主线程上安全返回错误
private fun sendErrorOnMainThread(
result: Result,
errorCode: String,
errorMessage: String,
errorDetails: Any?
) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接返回
result.error(errorCode, errorMessage, errorDetails)
} else {
// 否则切换到主线程
mainHandler.post {
result.error(errorCode, errorMessage, errorDetails)
}
}
}
private val bluetoothCallback = object : BtEventCallback() { private val bluetoothCallback = object : BtEventCallback() {
override fun onConnection(device: BluetoothDevice?, status: Int) { override fun onConnection(device: BluetoothDevice?, status: Int) {
if ((status == StateCode.CONNECTION_FAILED || status == StateCode.CONNECTION_DISCONNECT) && bluetoothHelper.getConnectedDevice() != null) { if ((status == StateCode.CONNECTION_FAILED || status == StateCode.CONNECTION_DISCONNECT) && bluetoothHelper.getConnectedDevice() != null) {
bluetoothHelper.disconnectDevice(bluetoothHelper.getConnectedDevice()) bluetoothHelper.disconnectDevice(bluetoothHelper.getConnectedDevice())
} }
otaDevice = device!!
// sendEventOnMainThread(
// mapOf(
// "event" to "NOCONNECTION"
// )
// )
// 更新连接状态的 LiveData,用于通知 UI 或其他逻辑 // 更新连接状态的 LiveData,用于通知 UI 或其他逻辑
otaConnectionMLD.value = DeviceConnection(device, status) // otaConnectionMLD.value = DeviceConnection(device, status)
Log.d("bluetoothCallback", "onConnection,status${status}") Log.d("bluetoothCallback", "onConnection,status${status}")
if( status == StateCode.CONNECTION_OK) { if (status == StateCode.CONNECTION_OK) {
Log.d("TAG", "startUpgrade") Log.d("TAG", "startUpgrade")
val directoryName = "ota_upgrades"
val fileName = "1.ufw"
// 创建WAV输出文件路径
val filePath = context.getExternalFilesDir(null)?.absolutePath + "/downloads/ota_upgrades/$fileName"
sendEventOnMainThread(
mapOf(
"event" to "CONNECTION_SUCCESS"
)
)
Log.d("TAG", "startUpgrade,filePath=${filePath}") Log.d("TAG", "startUpgrade,filePath=${filePath}")
otaManager.bluetoothOption.firmwareFilePath = filePath otaManager.bluetoothOption.firmwareFilePath = filePath
otaManager.startOTA( otaManager.startOTA(
CustomUpdateCallback( CustomUpdateCallback(
otaManager.bluetoothHelper.getConnectedDevice() otaManager.bluetoothHelper.getConnectedDevice(), streamSink
) )
) )
} }
} }
override fun onMandatoryUpgrade(device: BluetoothDevice?) { override fun onMandatoryUpgrade(device: BluetoothDevice?) {
mandatoryUpgradeMLD.postValue(device!!) // mandatoryUpgradeMLD.postValue(device!!)
Log.d("bluetoothCallback", "onMandatoryUpgrade") Log.d("bluetoothCallback", "onMandatoryUpgrade")
} }
} }
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext context = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "ota") channel = MethodChannel(flutterPluginBinding.binaryMessenger, "ota")
channel.setMethodCallHandler(this) channel.setMethodCallHandler(this)
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, EVENT_CHANNEL_NAME)
Log.d("TAG", "onAttachedToEngine") eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
streamSink = events
} }
override fun onMethodCall(call: MethodCall, result: Result) { override fun onCancel(arguments: Any?) {
if (call.method == "getPlatformVersion") { streamSink = null
Log.d("TAG", "Connection") }
// otaManager.configHelper })
// 确保先初始化 ConfigHelper // 初始化 otaManager 和 bluetoothHelper
ConfigHelper.initialize(context) ConfigHelper.initialize(context)
otaManager = OTAManager(context) otaManager = OTAManager(context)
bluetoothHelper = BluetoothHelper(context) bluetoothHelper = BluetoothHelper(context)
// 如果需要额外的初始化逻辑,请在这里添加 // 如果需要额外的初始化逻辑,请在这里添加
otaManager.registerBluetoothCallback(bluetoothCallback) otaManager.registerBluetoothCallback(bluetoothCallback)
BleService.addCallback(this) BleService.addCallback(this)
// otaManager.registerBluetoothCallback(bluetoothCallback) isRegisteredToBle = true
result.success("Android ${android.os.Build.VERSION.RELEASE}") Log.i("TAG", "onAttachedToEngine")
}else if (call.method == "searchDevices") {
Log.d("TAG", "searchDevices")
BleService.isota=true
device1 = BleService.bluetoothGatt?.device!!
BleService.disconnect()
}
} else if (call.method == "startUpgrade") { override fun onMethodCall(call: MethodCall, result: Result) {
Log.d("TAG", "startUpgrade") if (call.method == "searchDevices") {
val directoryName = "ota_upgrades" Log.d("TAG", "searchDevices")
val fileName = "1.ufw" // 接收传入的参数
// 创建WAV输出文件路径 val arguments = call.arguments as? Map<String, Any>
val filePath = context.getExternalFilesDir(null)?.absolutePath + "/downloads/ota_upgrades/$fileName" filePath = (arguments?.get("filePath") as? String).toString()
Log.d("TAG", "startUpgrade,filePath=${filePath}") if (filePath != null) {
Log.d("TAG", "Received filePath: $filePath")
// 在这里使用 filePath 参数,例如传递给其他方法或类
} else {
Log.d("TAG", "filePath 参数为空")
}
BleService.isEnterOta = true
//var filePath = "/storage/emulated/0/Download/JieLiOTA/upgrade/update - v0.1.0_添加杰理OTA,优化主从切换拾音丢失问题.ufw" val bluetoothGatt = BleService.bluetoothGatt
otaManager.bluetoothOption.firmwareFilePath = filePath if (bluetoothGatt?.device != null) {
otaManager.startOTA( isDisconnect = true
CustomUpdateCallback( otaDevice = bluetoothGatt.device
otaManager.bluetoothHelper.getConnectedDevice() BleService.disconnect()
} else {
Log.e("TAG", "BluetoothGatt 或 device 为 null,无法执行操作")
sendEventOnMainThread(
mapOf(
"event" to "NOCONNECTION"
) )
) )
result.success("Android ${android.os.Build.VERSION.RELEASE}") BleService.isEnterOta = false
}
} else if (call.method == "onOtaClose") {
BleService.isEnterOta = false
isDisconnect = false
} else { } else {
result.notImplemented() result.notImplemented()
} }
@ -174,8 +331,18 @@ private lateinit var bluetoothHelper : BluetoothHelper
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null) channel.setMethodCallHandler(null)
eventChannel.setStreamHandler(null)
// 取消BLE回调注册
if (isRegisteredToBle) {
BleService.removeCallback(this)
isRegisteredToBle = false
}
isDisconnect = false
// 释放其他资源
streamSink = null
BleService.isEnterOta = false
Log.d(TAG, "OtaPlugin 已释放资源")
} }
override fun onScanResult(devices: List<Map<String, Any>>) { override fun onScanResult(devices: List<Map<String, Any>>) {
@ -183,17 +350,10 @@ private lateinit var bluetoothHelper : BluetoothHelper
} }
override fun onConnectionStateChanged(state: Int) { override fun onConnectionStateChanged(state: Int) {
// // 可选:处理连接状态变化 if (state == BleConst.STATE_DISCONNECTED && isDisconnect) {
// if (state == BleConst.STATE_CONNECTED) {
// Log.d(TAG, "Ble设备已连接,准备好语音交互")
// } else if (state == BleConst.STATE_DISCONNECTED) {
Log.d("TAG", "Ble设备已断开,onConnectionStateChanged") Log.d("TAG", "Ble设备已断开,onConnectionStateChanged")
// // 设备断开连接,停止语音服务
// // stopListening()
// }
if (state == BleConst.STATE_DISCONNECTED) {
Handler(Looper.getMainLooper()).postDelayed({ Handler(Looper.getMainLooper()).postDelayed({
otaManager.bluetoothHelper.startScan(device1) otaManager.bluetoothHelper.startScan(otaDevice)
}, 2000) // 2000 毫秒 = 2 秒 }, 2000) // 2000 毫秒 = 2 秒
} }
} }

7
local_plugins/ota/android/src/main/kotlin/com/example/ota/tool/ota/OTAManager.kt

@ -16,6 +16,7 @@ import com.jieli.otasdk.tool.config.ConfigHelper
import com.jieli.otasdk.tool.bluetooth.OtaConstant import com.jieli.otasdk.tool.bluetooth.OtaConstant
import java.util.* import java.util.*
import android.util.Log import android.util.Log
/** /**
* 用于RCSP的第三方SDK接入OTA流程 * 用于RCSP的第三方SDK接入OTA流程
*/ */
@ -23,6 +24,7 @@ class OTAManager(context: Context) : BluetoothOTAManager(context) {
val configHelper = ConfigHelper.getInstance() val configHelper = ConfigHelper.getInstance()
val bluetoothHelper = BluetoothHelper.getInstance(context) val bluetoothHelper = BluetoothHelper.getInstance(context)
/** /**
* 转换成OTA库的连接状态 * 转换成OTA库的连接状态
* *
@ -36,12 +38,14 @@ class OTAManager(context: Context) : BluetoothOTAManager(context) {
2 -> { 2 -> {
changeStatus = StateCode.CONNECTION_OK changeStatus = StateCode.CONNECTION_OK
} }
1 -> { 1 -> {
changeStatus = StateCode.CONNECTION_CONNECTING changeStatus = StateCode.CONNECTION_CONNECTING
} }
} }
return changeStatus return changeStatus
} }
val btEventCallback = object : OnBTEventCallback() { val btEventCallback = object : OnBTEventCallback() {
override fun onDeviceConnection(device: BluetoothDevice?, way: Int, status: Int) { override fun onDeviceConnection(device: BluetoothDevice?, way: Int, status: Int) {
@ -98,7 +102,8 @@ class OTAManager(context: Context) : BluetoothOTAManager(context) {
bluetoothOption.isUseReconnect = bluetoothOption.isUseReconnect =
(configHelper.isUseCustomReConnectWay() && configHelper.isHidDevice()) (configHelper.isUseCustomReConnectWay() && configHelper.isHidDevice())
Log.d( Log.d(
TAG, "是否启用设备认证流程(与固件工程师确认) >>> ${configHelper.isUseDeviceAuth()} >>> ${bluetoothOption.isUseReconnect} " TAG,
"是否启用设备认证流程(与固件工程师确认) >>> ${configHelper.isUseDeviceAuth()} >>> ${bluetoothOption.isUseReconnect} "
) )
//是否启用设备认证流程(与固件工程师确认) //是否启用设备认证流程(与固件工程师确认)
bluetoothOption.isUseAuthDevice = configHelper.isUseDeviceAuth() bluetoothOption.isUseAuthDevice = configHelper.isUseDeviceAuth()

145
local_plugins/ota/lib/ota.dart

@ -1,19 +1,148 @@
import 'ota_platform_interface.dart'; import 'ota_platform_interface.dart';
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
class OtaEvent {
static final int NOT_STARTED = -1; // 未开始
static final int VALIDATION_FILE = 0; // 验证文件
static final int ONUPGRADE = 1; // 升级中
static final int START_UPGRADING = 2; // 开始升级
static final int UPGRADE_SUCCESS = 3; // 升级成功
static final int UPGRADE_FAILED = 4; // 升级失败
static final int CONNECTION_DISCONNECTION = 5; // 连接断开
static final int CONNECTION_SUCCESS = 6; // 连接成功
/// 事件类型
final String event;
/// 输出升级状态
final int? type;
/// 输出升级进度
final double? progress;
/// 错误码 (仅在发生错误时有效)
final int? code;
/// 错误信息 (仅在发生错误时有效)
final String? message;
/// 构造函数
OtaEvent({
required this.event,
this.type,
this.progress,
this.code,
this.message,
});
/// 从Map构造对象
factory OtaEvent.fromMap(Map<dynamic, dynamic> map) {
return OtaEvent(
event: map['event'],
type: map['type'],
progress: map['progress'],
code: map['code'],
message: map['message'],
);
}
}
class Ota { class Ota {
Future<String?> getPlatformVersion() { // 单例实例
return OtaPlatform.instance.getPlatformVersion(); static final Ota _instance = Ota._internal();
// 私有构造函数
Ota._internal() {
// 初始化时设置事件监听
_setupEventListener();
}
// 公共访问点
static Ota get instance => _instance;
static const EventChannel _eventChannel = EventChannel(
'com.example.ota/events',
);
final RxDouble progress = 0.0.obs;
final RxInt type = OtaEvent.NOT_STARTED.obs;
final RxBool isUpgrade = false.obs;
/// 事件流
Stream<OtaEvent>? _eventStream;
bool _isEventListenerSet = false; // 防止重复监听
/// 获取事件流
Stream<OtaEvent> get eventStream {
_eventStream ??= _eventChannel.receiveBroadcastStream().map((event) {
if (event is Map) {
return OtaEvent.fromMap(event);
}
throw PlatformException(code: 'INVALID_EVENT', message: '无效的事件数据');
});
return _eventStream!;
}
/// 释放资源
void onClose() {
onOtaClose();
// 停止事件监听
_eventStream = null;
_isEventListenerSet = false;
// 清理 Rx 对象
progress.close();
type.close();
isUpgrade.value = false;
print("Ota 对象资源已释放");
}
/// 内部设置事件监听(只执行一次)
void _setupEventListener() {
if (_isEventListenerSet) return;
eventStream.listen((event) {
if (event.event == 'onProgress' &&
event.type != null &&
event.progress != null) {
print("flutter 收到数据${event.progress}type=${type.value}");
progress.value = event.progress!;
type.value = event.type!;
}
if (event.event == 'onStartOTA') {
type.value = OtaEvent.START_UPGRADING;
isUpgrade.value = true; // 开始升级
}
if (event.event == 'onStopOTA') {
type.value = OtaEvent.UPGRADE_SUCCESS;
isUpgrade.value = false;
}
if (event.event == 'onError' &&
event.code != null &&
event.message != null) {
print("flutter onError${event.code}type=${event.message}");
type.value = OtaEvent.UPGRADE_FAILED;
isUpgrade.value = false;
}
if (event.event == 'NOCONNECTION') {
type.value = OtaEvent.CONNECTION_DISCONNECTION;
isUpgrade.value = false;
}
if (event.event == 'CONNECTION_SUCCESS') {
type.value = OtaEvent.CONNECTION_SUCCESS;
} }
});
Future<String?> searchDevices() { _isEventListenerSet = true;
return OtaPlatform.instance.searchDevices();
} }
Future<String?> connectDevice() { Future<String?> searchDevices(String? filePath) {
return OtaPlatform.instance.connectDevice(); return OtaPlatform.instance.searchDevices(filePath);
} }
Future<String?> startUpgrade() { Future<String?> onOtaClose() {
return OtaPlatform.instance.startUpgrade(); return OtaPlatform.instance.onOtaClose();
} }
} }

20
local_plugins/ota/lib/ota_method_channel.dart

@ -10,25 +10,17 @@ class MethodChannelOta extends OtaPlatform {
final methodChannel = const MethodChannel('ota'); final methodChannel = const MethodChannel('ota');
@override @override
Future<String?> getPlatformVersion() async { Future<String?> searchDevices(String? filePath) async {
final version = await methodChannel.invokeMethod<String>( final version = await methodChannel.invokeMethod<String>(
'getPlatformVersion', 'searchDevices',
{'filePath': filePath}, // 传递参数
); );
return version; return version;
} }
Future<String?> searchDevices() async { @override
final version = await methodChannel.invokeMethod<String>('searchDevices'); Future<String?> onOtaClose() async {
return version; final version = await methodChannel.invokeMethod<String>('onOtaClose');
}
Future<String?> connectDevice() async {
final version = await methodChannel.invokeMethod<String>('connectDevice');
return version;
}
Future<String?> startUpgrade() async {
final version = await methodChannel.invokeMethod<String>('startUpgrade');
return version; return version;
} }
} }

12
local_plugins/ota/lib/ota_platform_interface.dart

@ -23,19 +23,11 @@ abstract class OtaPlatform extends PlatformInterface {
_instance = instance; _instance = instance;
} }
Future<String?> getPlatformVersion() { Future<String?> searchDevices(String? filePath) {
throw UnimplementedError('platformVersion() has not been implemented.'); throw UnimplementedError('platformVersion() has not been implemented.');
} }
Future<String?> searchDevices() { Future<String?> onOtaClose() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
Future<String?> connectDevice() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
Future<String?> startUpgrade() {
throw UnimplementedError('platformVersion() has not been implemented.'); throw UnimplementedError('platformVersion() has not been implemented.');
} }
} }

2
local_plugins/ota/pubspec.yaml

@ -11,7 +11,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
plugin_platform_interface: ^2.0.2 plugin_platform_interface: ^2.0.2
get: ^4.6.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter

Loading…
Cancel
Save