diff --git a/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart b/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart index fa623f5fd..3e71cb1fd 100644 --- a/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart +++ b/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart @@ -11,18 +11,28 @@ 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 RxBool isFetchingDeviceInfo = false.obs; - final Dio _dio = Dio(); final RxString latestVersion = ''.obs; final RxString currentVersion = ''.obs; String updatepackageaddress = ''; 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; - var jlota = Ota(); + final jlota = Ota.instance; + + final RxBool isDownloading = false.obs; // 蓝牙服务 final _bleManager = Get.find(); @override @@ -31,30 +41,57 @@ class OtaUpgradeController extends GetxController { _getCurrentVersion(); } + @override + void onClose() { + jlota.onOtaClose(); + 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 startUpdate() async { try { - isUpdating.value = true; checkUpdate(); // 实现更新逻辑... } catch (e) { errorMessage.value = e.toString(); Get.snackbar('更新失败', e.toString()); - } finally { - isUpdating.value = false; } } Future _getCurrentVersion() async { try { - jlota.getPlatformVersion(); // 添加获取设备信息的重试机制 DeviceInfo? deviceInfo = _bleManager.deviceInfo.value; @@ -158,12 +195,18 @@ class OtaUpgradeController extends GetxController { content: Text('是否立即更新?'), actions: [ TextButton( - onPressed: () => Get.back(), + onPressed: () { + if (Get.isDialogOpen!) { + Get.back(); // 确保只关闭对话框 + } + }, child: Text('取消'), ), TextButton( onPressed: () { - Get.back(); + if (Get.isDialogOpen!) { + Get.back(); // 确保只关闭对话框 + } startDownload(); }, child: Text('更新'), @@ -175,7 +218,7 @@ class OtaUpgradeController extends GetxController { // 修改后的startDownload方法 Future startDownload() async { - isUpdating.value = true; + isDownloading.value = true; int lastIndex = updatepackageaddress.lastIndexOf('/'); String fileName = updatepackageaddress.substring(lastIndex + 1); String directoryName = "ota_upgrades"; @@ -187,8 +230,7 @@ class OtaUpgradeController extends GetxController { headers: {'Accept-Encoding': 'gzip'}, )); - Get.snackbar('升级命令已发送', '开始下载...'); - + jlota.type.value = DOWNLOADING_UP_FILES; try { final Directory? downloadsDir = await getDownloadsDirectory(); if (downloadsDir == null) { @@ -217,13 +259,13 @@ class OtaUpgradeController extends GetxController { } } catch (e) { Get.snackbar('错误', '检查本地文件失败: $e'); - isUpdating.value = false; + return; } if (existingSameFile) { Get.snackbar('文件已存在', '跳过下载,直接更新'); - isUpdating.value = false; + _installApp(savePath); return; } @@ -238,7 +280,7 @@ class OtaUpgradeController extends GetxController { } } catch (e) { Get.snackbar('错误', '删除旧文件失败: $e'); - isUpdating.value = false; + //isUpgrade.value = false; return; } @@ -249,59 +291,39 @@ class OtaUpgradeController extends GetxController { followRedirects: true, maxRedirects: 5, ); - - DateTime lastUpdate = DateTime.now(); + // 修改下载部分 await dio.download( updatepackageaddress, savePath, options: options, onReceiveProgress: (received, total) { - final now = DateTime.now(); - if (now.difference(lastUpdate).inMilliseconds > 1000) { - 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), - ); + if (total != -1) { + downloadProgress.value = received / total; } }, ); - File file = File(savePath); if (await file.exists()) { Get.snackbar('下载完成', '文件已保存至: $savePath'); } else { Get.snackbar('错误', '文件未找到,请检查写入权限'); } - isUpdating.value = false; + isDownloading.value = false; _installApp(savePath); + return; } on DioException catch (e) { Get.snackbar('下载失败', '错误: ${e.message}'); - isUpdating.value = false; } on FileSystemException catch (e) { Get.snackbar('存储失败', '无法写入文件: ${e.message}'); - isUpdating.value = false; } catch (e) { Get.snackbar('未知错误', e.toString()); - isUpdating.value = false; } } // 开始升级 void _installApp(String path) async { - jlota.searchDevices(); - // jlota.startUpgrade(); - // JlOtaPlugin() - // final File file = File(path); - // if (await file.exists()) { - // // OpenResult result = await OpenFile.open(path); - // // if (result.type != ResultType.done) { - // // Get.snackbar('错误', '安装失败: ${result.message}'); - // // } - // } + jlota.searchDevices(path); + return; + // isUpgrade.value = false; } } diff --git a/lib/modules/ota_upgrade/views/ota_upgrade_view.dart b/lib/modules/ota_upgrade/views/ota_upgrade_view.dart index 4555f63c8..1173a130d 100644 --- a/lib/modules/ota_upgrade/views/ota_upgrade_view.dart +++ b/lib/modules/ota_upgrade/views/ota_upgrade_view.dart @@ -101,7 +101,7 @@ class OtaUpgradeView extends GetView { fontSize: 18, color: isDarkMode ? Colors.grey[300] : Colors.black)), SizedBox(width: 8), // 改为width - Obx(() => Text('v${controller.currentVersion}', + Obx(() => Text('V ${controller.currentVersion}', style: TextStyle( fontSize: 24, fontWeight: FontWeight.normal, @@ -117,11 +117,11 @@ class OtaUpgradeView extends GetView { fontSize: 18, color: isDarkMode ? Colors.grey[300] : Colors.black)), SizedBox(width: 8), // 改为width - Text('v${controller.latestVersion}', + Obx(() => Text('V ${controller.latestVersion}', style: TextStyle( fontSize: 24, 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 { ); Widget _buildUpdateButton(bool isDarkMode) { - return controller.isUpdating.value + return controller.jlota.isUpgrade.value ? _buildProgressIndicator(isDarkMode) : Container( width: double.infinity, @@ -262,7 +262,7 @@ class OtaUpgradeView extends GetView { if (controller.isUpdateAvailable.value) ...[ SizedBox(height: 8), Text( - '建议连接WiFi并保持充电状态'.tr, + '取出双耳机,并保证电量均为30%以上的状态'.tr, style: TextStyle( fontSize: 14, color: isDarkMode ? Colors.grey[400] : Colors.grey[600], @@ -274,61 +274,67 @@ class OtaUpgradeView extends GetView { ); } - Widget _buildProgressIndicator(bool isDarkMode) => Container( - padding: EdgeInsets.all(20), - decoration: BoxDecoration( - color: - isDarkMode ? Colors.blue[900]!.withOpacity(0.2) : Colors.blue[50], - borderRadius: BorderRadius.circular(16), - ), + Widget _buildProgressIndicator(bool isDarkMode) { + return Obx(() { + final progress = controller.jlota.progress.value; + final type = controller.jlota.type.value; + + // // 处理升级失败状态:弹出 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( children: [ - Text('正在下载更新...'.tr, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - 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), + Text( + controller.getStatusMessage(type), + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: isDarkMode ? Colors.blue[300] : Colors.blue[700], ), - 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( + isDarkMode ? Colors.blue[300]! : Colors.blue, + ), + ), + const SizedBox(height: 8), + Text( + '${progress.toStringAsFixed(1)}%', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: isDarkMode ? Colors.blue[300] : Colors.blue[700], ), ), - SizedBox(height: 16), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.downloading, - color: isDarkMode ? Colors.blue[300] : Colors.blue, - size: 20, - ), - SizedBox(width: 8), - Text( - '${(controller.downloadProgress.value * 100).toStringAsFixed(1)}%', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: - isDarkMode ? Colors.blue[300] : Colors.blue[700])), - ], - ) ], ), ); + }); + } } diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt index 2ab2d1e30..c000e1faa 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt +++ b/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.exceptions.OpusException 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.constant.ErrorCode; import com.jieli.jl_bt_ota.constant.StateCode; @@ -89,7 +88,8 @@ object BleService { private lateinit var scanHandler: Handler private val scanResults = ArrayList() private var isScanning = false - var isota = false +// 是否是ota模式 + var isEnterOta = false // 连接状态 val connectionState = MutableLiveData(BleConst.STATE_DISCONNECTED) @@ -354,9 +354,10 @@ object BleService { return } - Log.i(TAG, "检测到已配对设备,MAC: $pairedMac,尝试连接") - if(!isota) + + if(!isEnterOta) { + Log.i(TAG, "检测到已配对设备,MAC: $pairedMac,尝试连接") // 调用现有的 connect 方法 connect(pairedMac) } @@ -430,44 +431,7 @@ object BleService { 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}") - // // 处理错误 - // } - // } + /** * 更新连接状态并通知回调 diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/OTAManager.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/OTAManager.kt deleted file mode 100644 index b28149983..000000000 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/OTAManager.kt +++ /dev/null @@ -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 - } -} - diff --git a/local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt b/local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt index 61f954136..7dcc198b0 100644 --- a/local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt +++ b/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.FileOutputStream 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 { 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" + private const val CHANNEL_NAME = "com.example.ota" + private const val EVENT_CHANNEL_NAME = "com.example.ota/events" } override fun 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) { 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) { 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() { Log.d(TAG, "onStartOTA停止升级") - + if (streamSink != null) { + if (Looper.myLooper() == Looper.getMainLooper()) { + streamSink.success( + mapOf( + "event" to "onStopOTA", + ) + ) + } + } + BleService.isEnterOta = false } override fun onCancelOTA() { Log.d(TAG, "onCancelOTA取消升级") - + if (streamSink != null) { + if (Looper.myLooper() == Looper.getMainLooper()) { + streamSink.success( + mapOf( + "event" to "onCancelOTA", + ) + ) + } + } } override fun onError(error: BaseError?) { 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()) Log.d(TAG, "完整错误对象: ${error ?: "null"}") } @@ -79,123 +147,215 @@ import java.io.RandomAccessFile /** OtaPlugin */ class OtaPlugin : BleService.Callback, FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity - private lateinit var channel : MethodChannel - private lateinit var context: Context - private lateinit var device1: BluetoothDevice -private lateinit var bluetoothHelper : BluetoothHelper - val otaConnectionMLD = MutableLiveData() - val mandatoryUpgradeMLD = MutableLiveData() - private lateinit var otaManager : OTAManager - - private val bluetoothCallback = object : BtEventCallback() { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel: MethodChannel + private lateinit var context: Context + private lateinit var otaDevice: BluetoothDevice + private lateinit var bluetoothHelper: BluetoothHelper + + 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) { + 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() { override fun onConnection(device: BluetoothDevice?, status: Int) { if ((status == StateCode.CONNECTION_FAILED || status == StateCode.CONNECTION_DISCONNECT) && bluetoothHelper.getConnectedDevice() != null) { bluetoothHelper.disconnectDevice(bluetoothHelper.getConnectedDevice()) + + } + otaDevice = device!! + // sendEventOnMainThread( + // mapOf( + // "event" to "NOCONNECTION" + // ) + // ) + // 更新连接状态的 LiveData,用于通知 UI 或其他逻辑 + // otaConnectionMLD.value = DeviceConnection(device, status) + Log.d("bluetoothCallback", "onConnection,status${status}") + if (status == StateCode.CONNECTION_OK) { + Log.d("TAG", "startUpgrade") + + sendEventOnMainThread( + mapOf( + "event" to "CONNECTION_SUCCESS" + ) + ) + Log.d("TAG", "startUpgrade,filePath=${filePath}") + otaManager.bluetoothOption.firmwareFilePath = filePath + otaManager.startOTA( + CustomUpdateCallback( + otaManager.bluetoothHelper.getConnectedDevice(), streamSink + ) + ) } - // 更新连接状态的 LiveData,用于通知 UI 或其他逻辑 - otaConnectionMLD.value = DeviceConnection(device, status) - Log.d("bluetoothCallback", "onConnection,status${status}") - if( status == StateCode.CONNECTION_OK) { - Log.d("TAG", "startUpgrade") - val directoryName = "ota_upgrades" - val fileName = "1.ufw" - // 创建WAV输出文件路径 - val filePath = context.getExternalFilesDir(null)?.absolutePath + "/downloads/ota_upgrades/$fileName" - - Log.d("TAG", "startUpgrade,filePath=${filePath}") - otaManager.bluetoothOption.firmwareFilePath = filePath - otaManager.startOTA( - CustomUpdateCallback( - otaManager.bluetoothHelper.getConnectedDevice() - ) - ) - } } override fun onMandatoryUpgrade(device: BluetoothDevice?) { - mandatoryUpgradeMLD.postValue(device!!) - Log.d("bluetoothCallback", "onMandatoryUpgrade") + // mandatoryUpgradeMLD.postValue(device!!) + Log.d("bluetoothCallback", "onMandatoryUpgrade") } } - override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { context = flutterPluginBinding.applicationContext - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "ota") - channel.setMethodCallHandler(this) - - Log.d("TAG", "onAttachedToEngine") - } - - override fun onMethodCall(call: MethodCall, result: Result) { - if (call.method == "getPlatformVersion") { - Log.d("TAG", "Connection") - // otaManager.configHelper - // 确保先初始化 ConfigHelper + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "ota") + channel.setMethodCallHandler(this) + eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, EVENT_CHANNEL_NAME) + + eventChannel.setStreamHandler(object : EventChannel.StreamHandler { + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + streamSink = events + } + + override fun onCancel(arguments: Any?) { + streamSink = null + } + }) + // 初始化 otaManager 和 bluetoothHelper ConfigHelper.initialize(context) - otaManager = OTAManager(context) - bluetoothHelper = BluetoothHelper(context) - // 如果需要额外的初始化逻辑,请在这里添加 - otaManager.registerBluetoothCallback(bluetoothCallback) - BleService.addCallback(this) - // otaManager.registerBluetoothCallback(bluetoothCallback) - result.success("Android ${android.os.Build.VERSION.RELEASE}") - }else if (call.method == "searchDevices") { - Log.d("TAG", "searchDevices") - BleService.isota=true - device1 = BleService.bluetoothGatt?.device!! - BleService.disconnect() - - - } else if (call.method == "startUpgrade") { - Log.d("TAG", "startUpgrade") - val directoryName = "ota_upgrades" - val fileName = "1.ufw" - // 创建WAV输出文件路径 - val filePath = context.getExternalFilesDir(null)?.absolutePath + "/downloads/ota_upgrades/$fileName" - - Log.d("TAG", "startUpgrade,filePath=${filePath}") - -//var filePath = "/storage/emulated/0/Download/JieLiOTA/upgrade/update - v0.1.0_添加杰理OTA,优化主从切换拾音丢失问题.ufw" - otaManager.bluetoothOption.firmwareFilePath = filePath - otaManager.startOTA( - CustomUpdateCallback( - otaManager.bluetoothHelper.getConnectedDevice() - ) - ) - result.success("Android ${android.os.Build.VERSION.RELEASE}") - } else { - result.notImplemented() - } - } - - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - } - - - - override fun onScanResult(devices: List>) { + otaManager = OTAManager(context) + bluetoothHelper = BluetoothHelper(context) + // 如果需要额外的初始化逻辑,请在这里添加 + otaManager.registerBluetoothCallback(bluetoothCallback) + BleService.addCallback(this) + isRegisteredToBle = true + Log.i("TAG", "onAttachedToEngine") + + } + + override fun onMethodCall(call: MethodCall, result: Result) { + if (call.method == "searchDevices") { + Log.d("TAG", "searchDevices") + // 接收传入的参数 + val arguments = call.arguments as? Map + filePath = (arguments?.get("filePath") as? String).toString() + + if (filePath != null) { + Log.d("TAG", "Received filePath: $filePath") + // 在这里使用 filePath 参数,例如传递给其他方法或类 + } else { + Log.d("TAG", "filePath 参数为空") + } + BleService.isEnterOta = true + + val bluetoothGatt = BleService.bluetoothGatt + if (bluetoothGatt?.device != null) { + isDisconnect = true + otaDevice = bluetoothGatt.device + BleService.disconnect() + + } else { + Log.e("TAG", "BluetoothGatt 或 device 为 null,无法执行操作") + sendEventOnMainThread( + mapOf( + "event" to "NOCONNECTION" + ) + ) + BleService.isEnterOta = false + + } + + + } else if (call.method == "onOtaClose") { + BleService.isEnterOta = false + isDisconnect = false + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + 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>) { // 不处理扫描结果 } override fun onConnectionStateChanged(state: Int) { - // // 可选:处理连接状态变化 - // if (state == BleConst.STATE_CONNECTED) { - // Log.d(TAG, "Ble设备已连接,准备好语音交互") - // } else if (state == BleConst.STATE_DISCONNECTED) { - Log.d("TAG", "Ble设备已断开,onConnectionStateChanged") - // // 设备断开连接,停止语音服务 - // // stopListening() - // } - if (state == BleConst.STATE_DISCONNECTED) { - Handler(Looper.getMainLooper()).postDelayed({ - otaManager.bluetoothHelper.startScan(device1) - }, 2000) // 2000 毫秒 = 2 秒 - } + if (state == BleConst.STATE_DISCONNECTED && isDisconnect) { + Log.d("TAG", "Ble设备已断开,onConnectionStateChanged") + Handler(Looper.getMainLooper()).postDelayed({ + otaManager.bluetoothHelper.startScan(otaDevice) + }, 2000) // 2000 毫秒 = 2 秒 + } } override fun onAudioDataReceived(data: ByteArray) { @@ -208,12 +368,12 @@ private lateinit var bluetoothHelper : BluetoothHelper * 在收到唤醒信号时启动语音识别 */ override fun onWakeupSignalReceived() { - + } override fun onDeviceInfoReceived(infoType: Int, infoData: Map) { // 不处理设备信息 } - + } diff --git a/local_plugins/ota/android/src/main/kotlin/com/example/ota/tool/ota/OTAManager.kt b/local_plugins/ota/android/src/main/kotlin/com/example/ota/tool/ota/OTAManager.kt index fdfa317bc..8001b29ee 100644 --- a/local_plugins/ota/android/src/main/kotlin/com/example/ota/tool/ota/OTAManager.kt +++ b/local_plugins/ota/android/src/main/kotlin/com/example/ota/tool/ota/OTAManager.kt @@ -16,33 +16,37 @@ import com.jieli.otasdk.tool.config.ConfigHelper import com.jieli.otasdk.tool.bluetooth.OtaConstant import java.util.* import android.util.Log + /** * 用于RCSP的第三方SDK接入OTA流程 */ class OTAManager(context: Context) : BluetoothOTAManager(context) { - val configHelper = ConfigHelper.getInstance() - val bluetoothHelper = BluetoothHelper.getInstance(context) - /** + val configHelper = ConfigHelper.getInstance() + val bluetoothHelper = BluetoothHelper.getInstance(context) + + /** * 转换成OTA库的连接状态 * * @param status 连接状态 * @return 库的连接状态 */ - fun changeConnectStatus(status: Int): Int { - var changeStatus = StateCode.CONNECTION_DISCONNECT - when (status) { - 2 -> { - changeStatus = StateCode.CONNECTION_OK - } - 1 -> { - changeStatus = StateCode.CONNECTION_CONNECTING - } + fun changeConnectStatus(status: Int): Int { + var changeStatus = StateCode.CONNECTION_DISCONNECT + when (status) { + 2 -> { + changeStatus = StateCode.CONNECTION_OK + } + + 1 -> { + changeStatus = StateCode.CONNECTION_CONNECTING } - return changeStatus } - val btEventCallback = object : OnBTEventCallback() { + return changeStatus + } + + val btEventCallback = object : OnBTEventCallback() { override fun onDeviceConnection(device: BluetoothDevice?, way: Int, status: Int) { super.onDeviceConnection(device, way, status) @@ -57,7 +61,7 @@ class OTAManager(context: Context) : BluetoothOTAManager(context) { TAG, "onConnection >>> device : ${device}, way = $way" + " status :$status, change status : $connectionState" ) - onBtDeviceConnection(device, connectionState) + onBtDeviceConnection(device, connectionState) } override fun onReceiveData( @@ -88,18 +92,19 @@ class OTAManager(context: Context) : BluetoothOTAManager(context) { } init { - // ConfigHelper.initialize(context) + // ConfigHelper.initialize(context) val bluetoothOption = BluetoothOTAConfigure() //选择通讯方式 bluetoothOption.priority = BluetoothOTAConfigure.PREFER_BLE - + //是否需要自定义回连方式(默认不需要,如需要自定义回连方式,需要客户自行实现) bluetoothOption.isUseReconnect = (configHelper.isUseCustomReConnectWay() && configHelper.isHidDevice()) - Log.d( - TAG, "是否启用设备认证流程(与固件工程师确认) >>> ${configHelper.isUseDeviceAuth()} >>> ${bluetoothOption.isUseReconnect} " - ) + Log.d( + TAG, + "是否启用设备认证流程(与固件工程师确认) >>> ${configHelper.isUseDeviceAuth()} >>> ${bluetoothOption.isUseReconnect} " + ) //是否启用设备认证流程(与固件工程师确认) bluetoothOption.isUseAuthDevice = configHelper.isUseDeviceAuth() //设置BLE的MTU diff --git a/local_plugins/ota/lib/ota.dart b/local_plugins/ota/lib/ota.dart index d3d2606f1..4df7c8d01 100644 --- a/local_plugins/ota/lib/ota.dart +++ b/local_plugins/ota/lib/ota.dart @@ -1,19 +1,148 @@ 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 map) { + return OtaEvent( + event: map['event'], + type: map['type'], + progress: map['progress'], + code: map['code'], + message: map['message'], + ); + } +} class Ota { - Future getPlatformVersion() { - return OtaPlatform.instance.getPlatformVersion(); + // 单例实例 + static final Ota _instance = Ota._internal(); + + // 私有构造函数 + Ota._internal() { + // 初始化时设置事件监听 + _setupEventListener(); } - Future searchDevices() { - return OtaPlatform.instance.searchDevices(); + // 公共访问点 + 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? _eventStream; + bool _isEventListenerSet = false; // 防止重复监听 + + /// 获取事件流 + Stream 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; + } + }); + + _isEventListenerSet = true; } - Future connectDevice() { - return OtaPlatform.instance.connectDevice(); + Future searchDevices(String? filePath) { + return OtaPlatform.instance.searchDevices(filePath); } - Future startUpgrade() { - return OtaPlatform.instance.startUpgrade(); + Future onOtaClose() { + return OtaPlatform.instance.onOtaClose(); } } diff --git a/local_plugins/ota/lib/ota_method_channel.dart b/local_plugins/ota/lib/ota_method_channel.dart index 56e8fde86..1dffdee59 100644 --- a/local_plugins/ota/lib/ota_method_channel.dart +++ b/local_plugins/ota/lib/ota_method_channel.dart @@ -10,25 +10,17 @@ class MethodChannelOta extends OtaPlatform { final methodChannel = const MethodChannel('ota'); @override - Future getPlatformVersion() async { + Future searchDevices(String? filePath) async { final version = await methodChannel.invokeMethod( - 'getPlatformVersion', + 'searchDevices', + {'filePath': filePath}, // 传递参数 ); return version; } - Future searchDevices() async { - final version = await methodChannel.invokeMethod('searchDevices'); - return version; - } - - Future connectDevice() async { - final version = await methodChannel.invokeMethod('connectDevice'); - return version; - } - - Future startUpgrade() async { - final version = await methodChannel.invokeMethod('startUpgrade'); + @override + Future onOtaClose() async { + final version = await methodChannel.invokeMethod('onOtaClose'); return version; } } diff --git a/local_plugins/ota/lib/ota_platform_interface.dart b/local_plugins/ota/lib/ota_platform_interface.dart index 77ade98ad..6c9c739ed 100644 --- a/local_plugins/ota/lib/ota_platform_interface.dart +++ b/local_plugins/ota/lib/ota_platform_interface.dart @@ -23,19 +23,11 @@ abstract class OtaPlatform extends PlatformInterface { _instance = instance; } - Future getPlatformVersion() { + Future searchDevices(String? filePath) { throw UnimplementedError('platformVersion() has not been implemented.'); } - Future searchDevices() { - throw UnimplementedError('platformVersion() has not been implemented.'); - } - - Future connectDevice() { - throw UnimplementedError('platformVersion() has not been implemented.'); - } - - Future startUpgrade() { + Future onOtaClose() { throw UnimplementedError('platformVersion() has not been implemented.'); } } diff --git a/local_plugins/ota/pubspec.yaml b/local_plugins/ota/pubspec.yaml index cb281e3be..6361a0944 100644 --- a/local_plugins/ota/pubspec.yaml +++ b/local_plugins/ota/pubspec.yaml @@ -11,12 +11,12 @@ dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.0.2 - + get: ^4.6.5 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 - + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec