From b1dc784c5d7777aed6f9e5b7f41318092d195d74 Mon Sep 17 00:00:00 2001 From: wolfplus Date: Sat, 3 May 2025 16:46:04 +0100 Subject: [PATCH] add --- .../yunqiinnovation/deepsound/MainActivity.kt | 27 +- lib/core/bindings/initial_binding.dart | 5 + .../bluetooth_media_button_service.dart | 310 ------- lib/data/services/bluetooth_service.dart | 249 ++++++ .../services/voice_interaction_service.dart | 350 -------- .../ble_test/bindings/ble_test_binding.dart | 11 - .../controllers/ble_test_controller.dart | 433 ---------- lib/modules/ble_test/views/ble_test_view.dart | 279 ------- .../chat/controllers/chat_controller.dart | 78 +- .../home/controllers/home_controller.dart | 352 ++++---- lib/modules/home/views/home_view.dart | 779 ++++++++++++------ .../controllers/pairing_controller.dart | 165 ++-- lib/modules/pairing/views/pairing_view.dart | 20 +- .../controllers/translation_controller.dart | 19 +- lib/routes/app_pages.dart | 8 +- .../ble_service/BleForegroundService.kt | 39 +- .../ble_service/BleManagerWrapper.kt | 23 +- .../yunqiinnovation/ble_service/BleService.kt | 198 +++-- .../ble_service/BleServicePlugin.kt | 313 +++---- .../ble_service/BleWakeReceiver.kt | 85 +- .../ble_service/lib/ble_service.dart | 229 +---- 21 files changed, 1454 insertions(+), 2518 deletions(-) delete mode 100644 lib/data/services/bluetooth_media_button_service.dart create mode 100644 lib/data/services/bluetooth_service.dart delete mode 100644 lib/data/services/voice_interaction_service.dart delete mode 100644 lib/modules/ble_test/bindings/ble_test_binding.dart delete mode 100644 lib/modules/ble_test/controllers/ble_test_controller.dart delete mode 100644 lib/modules/ble_test/views/ble_test_view.dart diff --git a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt index 872981220..d63ac7dbe 100644 --- a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt @@ -51,9 +51,6 @@ class MainActivity: FlutterActivity() { // 初始化FileLogger FileLogger.init(applicationContext) - // 初始化BLE后台唤醒功能 - // initBleBackgroundScan() - FileLogger.d(TAG, "初始化完成") } @@ -68,33 +65,13 @@ class MainActivity: FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) + + // 注册标准Flutter插件 GeneratedPluginRegistrant.registerWith(flutterEngine) } override fun onDestroy() { super.onDestroy() } - - /** - * 初始化BLE后台扫描和唤醒功能 - */ - private fun initBleBackgroundScan() { - try { - // 初始化BleManagerWrapper - val bleManager = BleManagerWrapper.instance - bleManager.init(applicationContext) - - // 启动BLE前台服务以确保后台扫描运行 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - startForegroundService(Intent(this, BleForegroundService::class.java)) - } else { - startService(Intent(this, BleForegroundService::class.java)) - } - - FileLogger.d(TAG, "成功启动BLE后台唤醒功能") - } catch (e: Exception) { - FileLogger.e(TAG, "BLE初始化失败", e) - } - } } diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart index 091f4e5e4..398e4f084 100644 --- a/lib/core/bindings/initial_binding.dart +++ b/lib/core/bindings/initial_binding.dart @@ -6,6 +6,7 @@ import '../../data/services/language_manager.dart'; import '../../data/services/volcano_ai_service.dart'; import '../../core/utils/logger.dart'; import '../../data/services/open_ai_service_adapter.dart'; +import '../../data/services/bluetooth_service.dart'; /// 初始绑定,用于管理全局依赖 class InitialBinding extends Bindings { @override @@ -32,6 +33,10 @@ class InitialBinding extends Bindings { adapter.initialize(); return adapter; }, fenix: true); + + + // 蓝牙服务 + Get.put(BluetoothService(), permanent: true); // 火山AI服务 Get.lazyPut(() => VolcanoAIService(), fenix: true); diff --git a/lib/data/services/bluetooth_media_button_service.dart b/lib/data/services/bluetooth_media_button_service.dart deleted file mode 100644 index 98864ba13..000000000 --- a/lib/data/services/bluetooth_media_button_service.dart +++ /dev/null @@ -1,310 +0,0 @@ -import 'dart:async'; -import 'dart:io' show Platform; -import 'package:flutter/services.dart'; -import 'package:get/get.dart'; -import '../../core/utils/logger.dart'; -import 'voice_interaction_service.dart'; -/// 蓝牙媒体按钮事件类型 -enum MediaButtonType { - /// 播放/暂停按钮 - playPause, - - /// 未知按钮 - unknown -} - -/// 蓝牙媒体按钮事件 -class MediaButtonEvent { - /// 按钮类型 - final MediaButtonType buttonType; - - /// 事件发生时间戳 - final int timestamp; - - MediaButtonEvent({ - required this.buttonType, - required this.timestamp, - }); - - factory MediaButtonEvent.fromMap(Map map) { - MediaButtonType buttonType; - - switch (map['buttonType']) { - case 'playPause': - buttonType = MediaButtonType.playPause; - break; - default: - // 将所有其他事件类型映射为 playPause - if (map['buttonType'] == 'play' || - map['buttonType'] == 'pause' || - map['buttonType'] == 'togglePlayPause') { - buttonType = MediaButtonType.playPause; - } else { - // 忽略其他类型(next, previous, seekForward, seekBackward) - buttonType = MediaButtonType.unknown; - } - } - - return MediaButtonEvent( - buttonType: buttonType, - timestamp: map['timestamp'] as int? ?? DateTime.now().millisecondsSinceEpoch, - ); - } - - @override - String toString() { - switch (buttonType) { - case MediaButtonType.playPause: - return '蓝牙媒体按钮: 播放/暂停'; - case MediaButtonType.unknown: - return '蓝牙媒体按钮: 未知'; - } - } -} - -/// 蓝牙媒体按钮服务 -class BluetoothMediaButtonService extends GetxService { - static BluetoothMediaButtonService get to => Get.find(); - - // 方法通道名称 - final MethodChannel _channel = const MethodChannel('com.deep_voice.bluetooth_media_button'); - - // 事件通道名称 - final EventChannel _eventChannel = const EventChannel('com.deep_voice.bluetooth_media_button_events'); - - /// 按钮事件流控制器 - final _buttonEventController = StreamController.broadcast(); - - /// 按钮事件流 - Stream get buttonEvents => _buttonEventController.stream; - - /// 是否已启用监听 - final _isListening = false.obs; - bool get isListening => _isListening.value; - - /// 是否正在加载 - final isLoading = false.obs; - - /// 错误信息 - final errorMessage = ''.obs; - - /// 按钮事件流订阅 - StreamSubscription? _buttonEventSubscription; - - /// 服务是否已初始化 - bool _isInitialized = false; - - /// 是否支持媒体按钮服务(iOS 13.0及以上) - bool get isSupported { - if (!Platform.isIOS) return true; // 非iOS平台默认支持 - - // 获取平台版本信息 - final String version = Platform.operatingSystemVersion; - - // 尝试提取iOS版本号 (格式如: "Version 14.5 (Build 18E182)") - try { - // 提取版本号 - final RegExp versionRegex = RegExp(r'Version\s+(\d+)\.(\d+)'); - final match = versionRegex.firstMatch(version); - - if (match != null) { - final int majorVersion = int.parse(match.group(1) ?? '0'); - return majorVersion >= 13; // iOS 13及以上支持 - } - } catch (e) { - Logger.error('解析iOS版本失败: $e'); - } - - // 无法确定版本时,假设不支持 - return false; - } - - /// 初始化服务 - BluetoothMediaButtonService() { - Logger.info('创建 BluetoothMediaButtonService 实例'); - } - - /// 初始化媒体按钮服务 - Future initialize() async { - if (_isInitialized) { - Logger.info('BluetoothMediaButtonService 已经初始化'); - return true; - } - - // 检查系统版本是否支持 - if (!isSupported) { - Logger.info('当前iOS版本不支持蓝牙媒体按钮功能(需要iOS 13.0+)'); - return false; - } - - try { - Logger.info('正在初始化 BluetoothMediaButtonService...'); - - // 设置事件监听器 - _setupButtonEventListener(); - - _isInitialized = true; - Logger.info('BluetoothMediaButtonService 初始化成功'); - return true; - } catch (e) { - Logger.error('初始化 BluetoothMediaButtonService 失败: $e'); - errorMessage.value = '初始化蓝牙媒体按钮服务失败'; - return false; - } - } - - /// 设置按钮事件监听器 - void _setupButtonEventListener() { - _buttonEventSubscription?.cancel(); - - // 设置事件通道监听器 - _buttonEventSubscription = _eventChannel - .receiveBroadcastStream() - .listen(_handleButtonEvent, onError: _handleEventStreamError); - - Logger.info('已设置蓝牙媒体按钮事件监听器'); - } - - /// 处理按钮事件 - void _handleButtonEvent(dynamic event) { - Logger.info('BluetoothMediaButtonService 收到原始事件: $event'); - - if (event is! Map) { - Logger.error('收到无效的蓝牙媒体按钮事件 (非Map类型): $event'); - return; - } - - final Map eventMap = event; - - try { - if (eventMap['type'] == 'mediaButtonEvent') { - // 获取按钮类型 - final String buttonType = eventMap['buttonType'] as String? ?? 'unknown'; - - // 判断是否为playPause相关事件 - if (buttonType == 'playPause' || - buttonType == 'play' || - buttonType == 'pause' || - buttonType == 'togglePlayPause') { - - // 创建playPause事件并广播 - final buttonEvent = MediaButtonEvent( - buttonType: MediaButtonType.playPause, - timestamp: eventMap['timestamp'] as int? ?? DateTime.now().millisecondsSinceEpoch, - ); - - - - // 广播事件 - _buttonEventController.add(buttonEvent); - - // 广播事件 - _buttonEventController.add(buttonEvent); - Logger.info('发送playPause按钮事件: $buttonEvent'); - } else { - // 忽略其他类型的按钮事件 - Logger.info('忽略非playPause按钮事件: $buttonType'); - } - } else { - Logger.warning('收到未知类型的事件: ${eventMap['type']}'); - } - } catch (e) { - Logger.error('处理蓝牙媒体按钮事件时出错: $e'); - } - } - - /// 处理事件流错误 - void _handleEventStreamError(dynamic error) { - Logger.error('蓝牙媒体按钮事件流错误: $error'); - errorMessage.value = '蓝牙媒体按钮监听出错'; - } - - /// 开始监听蓝牙媒体按钮事件 - Future startButtonListening() async { - // 检查系统版本是否支持 - if (!isSupported) { - Logger.info('当前iOS版本不支持蓝牙媒体按钮功能(需要iOS 13.0+)'); - return false; - } - - if (!_isInitialized && !(await initialize())) { - return false; - } - - if (_isListening.value) { - Logger.info('已经在监听蓝牙媒体按钮事件'); - return true; - } - - isLoading.value = true; - - try { - final result = await _channel.invokeMethod('startButtonListening') ?? false; - - if (result) { - _isListening.value = true; - Logger.info('开始监听蓝牙媒体按钮事件成功'); - } else { - errorMessage.value = '开始监听蓝牙媒体按钮事件失败'; - Logger.error('开始监听蓝牙媒体按钮事件失败'); - } - - isLoading.value = false; - return result; - } catch (e) { - isLoading.value = false; - errorMessage.value = '开始监听蓝牙媒体按钮事件出错: $e'; - Logger.error('开始监听蓝牙媒体按钮事件出错: $e'); - return false; - } - } - - /// 停止监听蓝牙媒体按钮事件 - Future stopButtonListening() async { - if (!_isInitialized) { - return false; - } - - if (!_isListening.value) { - return true; - } - - isLoading.value = true; - - try { - final result = await _channel.invokeMethod('stopButtonListening') ?? false; - - if (result) { - _isListening.value = false; - Logger.info('停止监听蓝牙媒体按钮事件成功'); - } else { - errorMessage.value = '停止监听蓝牙媒体按钮事件失败'; - Logger.error('停止监听蓝牙媒体按钮事件失败'); - } - - isLoading.value = false; - return result; - } catch (e) { - isLoading.value = false; - errorMessage.value = '停止监听蓝牙媒体按钮事件出错: $e'; - Logger.error('停止监听蓝牙媒体按钮事件出错: $e'); - return false; - } - } - - /// 释放资源 - void dispose() { - _buttonEventSubscription?.cancel(); - _buttonEventSubscription = null; - _buttonEventController.close(); - _isInitialized = false; - _isListening.value = false; - Logger.info('BluetoothMediaButtonService 已释放'); - } - - @override - void onClose() { - dispose(); - super.onClose(); - } -} \ No newline at end of file diff --git a/lib/data/services/bluetooth_service.dart b/lib/data/services/bluetooth_service.dart new file mode 100644 index 000000000..1e9028cca --- /dev/null +++ b/lib/data/services/bluetooth_service.dart @@ -0,0 +1,249 @@ +import 'dart:async'; +import 'dart:math' as math; +import 'package:get/get.dart'; +import 'package:classic_bluetooth/classic_bluetooth.dart'; +import 'package:deep_voice/core/utils/logger.dart'; +import 'package:flutter/services.dart'; + +/// 蓝牙设备信息缓存模型 +class DeviceInfo { + final String address; + final String name; + + // 整体电池电量 + final int batteryLevel; + + // 详细电池信息 + final int leftEarBattery; + final int rightEarBattery; + final int caseBattery; + + // 充电状态 + final bool leftEarCharging; + final bool rightEarCharging; + final bool caseCharging; + + final String firmwareVersion; + final String productId; + final String colorId; + final DateTime lastUpdated; + + DeviceInfo({ + required this.address, + required this.name, + this.batteryLevel = 0, + this.leftEarBattery = 0, + this.rightEarBattery = 0, + this.caseBattery = 0, + this.leftEarCharging = false, + this.rightEarCharging = false, + this.caseCharging = false, + this.firmwareVersion = '', + this.productId = '', + this.colorId = '', + DateTime? lastUpdated, + }) : lastUpdated = lastUpdated ?? DateTime.now(); + + /// 创建更新后的设备信息副本 + DeviceInfo copyWith({ + String? address, + String? name, + int? batteryLevel, + int? leftEarBattery, + int? rightEarBattery, + int? caseBattery, + bool? leftEarCharging, + bool? rightEarCharging, + bool? caseCharging, + String? firmwareVersion, + String? productId, + String? colorId, + }) { + return DeviceInfo( + address: address ?? this.address, + name: name ?? this.name, + batteryLevel: batteryLevel ?? this.batteryLevel, + leftEarBattery: leftEarBattery ?? this.leftEarBattery, + rightEarBattery: rightEarBattery ?? this.rightEarBattery, + caseBattery: caseBattery ?? this.caseBattery, + leftEarCharging: leftEarCharging ?? this.leftEarCharging, + rightEarCharging: rightEarCharging ?? this.rightEarCharging, + caseCharging: caseCharging ?? this.caseCharging, + firmwareVersion: firmwareVersion ?? this.firmwareVersion, + productId: productId ?? this.productId, + colorId: colorId ?? this.colorId, + lastUpdated: DateTime.now(), + ); + } + + @override + String toString() { + return 'DeviceInfo(address: $address, name: $name, ' + 'battery: $batteryLevel%, L: $leftEarBattery%, R: $rightEarBattery%, Case: $caseBattery%, ' + 'charging: L:$leftEarCharging, R:$rightEarCharging, C:$caseCharging, ' + 'version: $firmwareVersion, productId: $productId, colorId: $colorId)'; + } +} + +/// 蓝牙服务 - 管理经典蓝牙服务,提供设备信息缓存 +class BluetoothService extends GetxService { + static BluetoothService get to => Get.find(); + + // 蓝牙状态 + final _bluetoothState = 'unknown'.obs; + String get bluetoothState => _bluetoothState.value; + + // 当前经典蓝牙设备 + final Rx currentDeviceRx = Rx(null); + BluetoothDevice? get currentDevice => currentDeviceRx.value; + + // 设备信息缓存 + final Rx _deviceInfo = Rx(null); + DeviceInfo? get deviceInfo => _deviceInfo.value; + + // 表明当前连接类型,本服务只处理经典蓝牙 + bool get isBleConnection => false; + + // 订阅 + StreamSubscription? _bluetoothSubscription; + + /// 初始化 + @override + void onInit() { + super.onInit(); + Logger.info('蓝牙服务初始化'); + + // 设置蓝牙事件监听 + _setupBluetoothListener(); + } + + /// 释放资源 + @override + void onClose() { + _bluetoothSubscription?.cancel(); + super.onClose(); + } + + /// 设置经典蓝牙事件监听 + void _setupBluetoothListener() { + _bluetoothSubscription?.cancel(); + + try { + _bluetoothSubscription = ClassicBluetooth.events.listen( + (event) { + if (event.type == 'stateChanged' && event.state != null) { + _bluetoothState.value = event.state!; + Logger.info('蓝牙状态变更: ${event.state}'); + + // 如果蓝牙关闭,清除设备 + if (event.state == 'off') { + currentDeviceRx.value = null; + _deviceInfo.value = null; + } + } else if (event.type == 'deviceConnected' && event.device != null) { + // 直接从事件中获取设备信息 + final newDevice = event.device!; + + // 更新当前设备 + currentDeviceRx.value = newDevice; + + Logger.info('经典蓝牙设备已连接: ${newDevice.name} (${newDevice.address})'); + + // 创建基本设备信息 + if (_deviceInfo.value == null || _deviceInfo.value!.address != newDevice.address) { + _deviceInfo.value = DeviceInfo( + address: newDevice.address, + name: newDevice.name, + ); + Logger.info('创建设备信息缓存: ${_deviceInfo.value}'); + } + } else if (event.type == 'deviceDisconnected' && event.device != null) { + // 直接从事件中获取设备信息 + final disconnectedDevice = event.device!; + + Logger.info('经典蓝牙设备已断开: ${disconnectedDevice.name} (${disconnectedDevice.address})'); + + // 如果断开的是当前设备,清除当前设备 + if (currentDeviceRx.value?.address == disconnectedDevice.address) { + currentDeviceRx.value = null; + // 不清除设备信息缓存,保留最后状态 + Logger.info('已清除当前设备引用,保留最后设备信息缓存'); + } + } + }, + onError: (error) { + Logger.error('蓝牙事件流错误: $error'); + }, + ); + } catch (e) { + Logger.error('设置蓝牙事件监听器失败: $e'); + } + } + + /// 刷新设备连接状态 + Future refreshDeviceConnection() async { + try { + // 检查经典蓝牙连接 + final devices = await ClassicBluetooth.getConnectedDevices(); + + if (devices.isNotEmpty) { + final newDevice = devices.first; + + // 更新当前设备 + currentDeviceRx.value = newDevice; + + Logger.info('已连接经典蓝牙设备: ${newDevice.name} (${newDevice.address})'); + + // 创建基本设备信息 + if (_deviceInfo.value == null || _deviceInfo.value!.address != newDevice.address) { + _deviceInfo.value = DeviceInfo( + address: newDevice.address, + name: newDevice.name, + ); + Logger.info('创建设备信息缓存: ${_deviceInfo.value}'); + } + + return; + } + + // 没有任何连接 + currentDeviceRx.value = null; + Logger.info('未找到已连接的蓝牙设备'); + + } catch (e) { + Logger.error('刷新设备连接失败: $e'); + } + } + + /// 检查蓝牙是否已启用 + Future isBluetoothEnabled() async { + try { + final isEnabled = await ClassicBluetooth.isBluetoothEnabled(); + _bluetoothState.value = isEnabled ? 'on' : 'off'; + return isEnabled; + } catch (e) { + Logger.error('检查蓝牙状态失败: $e'); + return false; + } + } + + /// 启动BLE服务(不适用于此服务,仅为接口兼容) + Future startBleService(Map config) async { + Logger.warning('BluetoothService不支持BLE服务,请使用BleService'); + return false; + } + + /// 停止BLE服务(不适用于此服务,仅为接口兼容) + Future stopBleService() async { + Logger.warning('BluetoothService不支持BLE服务,请使用BleService'); + return false; + } + + /// 注册服务 + static Future init() async { + if (!Get.isRegistered()) { + Get.put(BluetoothService(), permanent: true); + } + return Future.value(Get.find()); + } +} \ No newline at end of file diff --git a/lib/data/services/voice_interaction_service.dart b/lib/data/services/voice_interaction_service.dart deleted file mode 100644 index 5a55f0219..000000000 --- a/lib/data/services/voice_interaction_service.dart +++ /dev/null @@ -1,350 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'package:deep_voice/data/models/appconfig_model.dart'; -import 'package:flutter/services.dart'; -import 'package:get/get.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:get_storage/get_storage.dart'; -import '../models/events/voice_interaction_event.dart'; -import '../../core/utils/logger.dart'; -import '../../modules/chat/models/message_model.dart'; -import '../../routes/app_routes.dart'; -import 'chat_history_service.dart'; - -/// 语音交互服务接口 -/// -/// 管理与平台原生语音交互服务的通信,提供统一的接口供应用使用 -class VoiceInteractionService extends GetxService { - static VoiceInteractionService get to => Get.find(); - final GetStorage _storage = GetStorage(); - // 方法通道 - static const MethodChannel _channel = - MethodChannel('com.deep_voice.voice_interaction'); - - // 事件通道 - static const EventChannel _eventChannel = - EventChannel('com.deep_voice.voice_interaction_events'); - - // 服务状态 - final _isServiceRunning = false.obs; - bool get isServiceRunning => _isServiceRunning.value; - - // 流控制器 - final _eventStreamController = - StreamController.broadcast(); - - // 事件流 - Stream get eventStream => - _eventStreamController.stream; - - // 事件通道订阅 - StreamSubscription? _eventSubscription; - - // 标记是否初始化 - bool _isInitialized = false; - - // 配置信息 - late String _azureSpeechKey; - late String _azureSpeechRegion; - late String _openaiApiKey; - late String _openaiBaseUrl; - late String _openaiModel; - late String _volcanoSpeechAppId; - late String _volcanoSpeechAppToken; - // MCP配置信息 - String? _mcpConfig; - - // 聊天历史服务 - late final ChatHistoryService _chatHistoryService; - - /// 设置事件通道 - void _setupEventChannel() { - _eventSubscription = _eventChannel - .receiveBroadcastStream() - .listen(_handleVoiceInteractionEvent, onError: (error) { - Logger.error('语音交互事件通道错误: $error'); - }); - } - - /// 从环境变量加载配置 - void _loadConfig() { - // _azureSpeechKey = dotenv.env['AZURE_SPEECH_KEY'] ?? ''; - // _azureSpeechRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? ''; - // _openaiApiKey = dotenv.env['OPENAI_API_KEY'] ?? ''; - // _openaiBaseUrl = dotenv.env['OPENAI_BASE_URL'] ?? ''; - // _openaiModel = dotenv.env['OPENAI_MODEL'] ?? ''; - // _volcanoSpeechAppId = dotenv.env['VOLCANO_SPEECH_APP_ID'] ?? ''; - // _volcanoSpeechAppToken = dotenv.env['VOLCANO_SPEECH_APP_TOKEN'] ?? ''; - final _env = _storage.read("ENV") as Map; - _azureSpeechKey = _env['AZURE_SPEECH_KEY'] ?? ''; - _azureSpeechRegion = _env['AZURE_SPEECH_REGION'] ?? ''; - _openaiApiKey = _env['OPENAI_API_KEY'] ?? ''; - _openaiBaseUrl = _env['OPENAI_BASE_URL'] ?? ''; - _openaiModel = _env['OPENAI_MODEL'] ?? ''; - _volcanoSpeechAppId = _env['VOLCANO_SPEECH_APP_ID'] ?? ''; - _volcanoSpeechAppToken = _env['VOLCANO_SPEECH_APP_TOKEN'] ?? ''; - - if (_azureSpeechKey.isEmpty || _azureSpeechRegion.isEmpty) { - Logger.warning( - '未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION'); - } - - // 加载MCP配置 - _loadMcpConfig(); - } - - /// 加载MCP配置 - void _loadMcpConfig() { - try { - final mcps = _storage.read("MCPS") as Map; - String config = jsonEncode(mcps); - _mcpConfig = config; - Logger.info('成功加载MCP配置, 配置内容: $_mcpConfig'); - // rootBundle.loadString('.mcp.json').then((config) { - // _mcpConfig = config; - // Logger.info('成功加载MCP配置, 配置内容: $_mcpConfig'); - // }); - } catch (e) { - Logger.warning('加载MCP配置失败: $e'); - _mcpConfig = null; - } - } - - /// 处理来自原生层的事件 - void _handleVoiceInteractionEvent(dynamic event) { - if (event is! Map) return; - - final eventMap = event as Map; - final String eventType = eventMap['type'] as String? ?? ''; - // final int timestamp = eventMap['timestamp'] as int? ?? DateTime.now().millisecondsSinceEpoch; - - Logger.info('收到语音交互事件: $eventType'); - - switch (eventType) { - case 'recognition_started': - // 语音识别开始事件 - final recognitionEvent = RecognitionStartedEvent( - timestamp: DateTime.now().millisecondsSinceEpoch, - ); - _eventStreamController.add(recognitionEvent); - break; - - case 'chat_history_updated': - // 聊天历史更新事件 - final String agentId = eventMap['agentId'] as String? ?? ''; - final String userMessage = eventMap['userMessage'] as String? ?? ''; - final String assistantMessage = - eventMap['assistantMessage'] as String? ?? ''; - - final chatHistoryEvent = ChatHistoryEvent( - agentId: agentId, - userMessage: userMessage, - assistantMessage: assistantMessage, - timestamp: DateTime.now().millisecondsSinceEpoch, - ); - _eventStreamController.add(chatHistoryEvent); - - // 保存聊天历史到ChatHistoryService - _saveChatHistory(agentId, userMessage, assistantMessage); - break; - - case 'enter_translation_mode': - // 进入翻译模式事件 - Logger.info('收到进入翻译模式事件,正在导航到翻译界面'); - _navigateToTranslation(); - - break; - } - } - - /// 导航到翻译界面 - void _navigateToTranslation() { - try { - // 使用GetX导航到翻译页面 - Get.offNamedUntil( - Routes.translation, (route) => route.settings.name == Routes.home); - Logger.info('已导航到翻译界面'); - } catch (e) { - Logger.error('导航到翻译界面失败: $e'); - } - } - - /// 保存聊天历史 - Future _saveChatHistory( - String agentId, String userMessage, String assistantMessage) async { - try { - final timestamp = DateTime.now().millisecondsSinceEpoch; - - // 创建用户消息和助手消息 - final userMsg = Message( - role: 'user', - content: userMessage, - timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp), - ); - - final assistantMsg = Message( - role: 'assistant', - content: assistantMessage, - timestamp: DateTime.fromMillisecondsSinceEpoch( - timestamp + 1), // 确保助手消息时间戳晚于用户消息 - ); - - // 加载现有历史记录 - final existingMessages = await _chatHistoryService.loadHistory(agentId); - - // 添加新消息 - existingMessages.addAll([userMsg, assistantMsg]); - - // 保存更新后的历史记录 - await _chatHistoryService.saveHistory(agentId, existingMessages); - - Logger.info('已保存聊天历史: agentId=$agentId'); - } catch (e) { - Logger.error('保存聊天历史失败: $e'); - } - } - - /// 初始化服务 - Future initialize() async { - if (_isInitialized) return true; - - try { - Logger.info('正在初始化语音交互服务...'); - - // 获取聊天历史服务 - try { - _chatHistoryService = Get.find(); - } catch (e) { - Logger.warning('获取ChatHistoryService失败,将创建新实例'); - _chatHistoryService = Get.put(ChatHistoryService()); - } - - // 设置事件通道 - _setupEventChannel(); - - // 加载配置 - _loadConfig(); - - // 检查服务是否已在运行 - final bool running = await checkServiceStatus(); - - // 如果服务未运行,启动服务 - if (!running) { - // 启动语音交互服务 - final success = await startService(); - if (!success) { - Logger.error('语音交互服务启动失败'); - return false; - } - } - - _isInitialized = true; - Logger.info('语音交互服务初始化完成'); - return true; - } catch (e) { - Logger.error('语音交互服务初始化失败: $e'); - return false; - } - } - - /// 启动语音交互服务 - Future startService() async { - try { - Logger.info('启动语音交互服务...'); - - // 构建参数 - final Map params = { - 'azure_speech_key': _azureSpeechKey, - 'azure_speech_region': _azureSpeechRegion, - 'openai_api_key': _openaiApiKey, - 'openai_base_url': _openaiBaseUrl, - 'openai_model': _openaiModel, - 'volcano_speech_app_id': _volcanoSpeechAppId, - 'volcano_speech_app_token': _volcanoSpeechAppToken, - }; - - // 如果有MCP配置,添加到参数中 - if (_mcpConfig != null) { - params['mcp_config'] = _mcpConfig; - } - - final result = - await _channel.invokeMethod('startService', params) ?? false; - - if (result) { - _isServiceRunning.value = true; - Logger.info('语音交互服务已启动'); - } else { - Logger.error('启动语音交互服务失败'); - } - - return result; - } catch (e) { - Logger.error('启动语音交互服务时发生错误: $e'); - return false; - } - } - - /// 停止语音交互服务 - Future stopService() async { - try { - Logger.info('停止语音交互服务...'); - - final result = await _channel.invokeMethod('stopService') ?? false; - - if (result) { - _isServiceRunning.value = false; - Logger.info('语音交互服务已停止'); - } else { - Logger.error('停止语音交互服务失败'); - } - - return result; - } catch (e) { - Logger.error('停止语音交互服务时发生错误: $e'); - return false; - } - } - - /// 检查服务是否运行 - Future checkServiceStatus() async { - try { - final bool result = - await _channel.invokeMethod('isServiceRunning') ?? false; - _isServiceRunning.value = result; - return result; - } catch (e) { - Logger.error('检查服务状态时发生错误: $e'); - return false; - } - } - - /// 暂停语音交互 - Future pauseVoiceInteraction() async { - try { - Logger.info('暂停后台语音交互...'); - - final result = - await _channel.invokeMethod('pauseVoiceInteraction') ?? false; - - if (result) { - Logger.info('语音交互已暂停'); - } else { - Logger.error('暂停语音交互失败'); - } - - return result; - } catch (e) { - Logger.error('暂停语音交互时发生错误: $e'); - return false; - } - } - - /// 资源释放 - @override - void onClose() { - _eventSubscription?.cancel(); - _eventStreamController.close(); - super.onClose(); - } -} diff --git a/lib/modules/ble_test/bindings/ble_test_binding.dart b/lib/modules/ble_test/bindings/ble_test_binding.dart deleted file mode 100644 index 0d4e90920..000000000 --- a/lib/modules/ble_test/bindings/ble_test_binding.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:get/get.dart'; -import '../controllers/ble_test_controller.dart'; - -class BleTestBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => BleTestController(), - ); - } -} \ No newline at end of file diff --git a/lib/modules/ble_test/controllers/ble_test_controller.dart b/lib/modules/ble_test/controllers/ble_test_controller.dart deleted file mode 100644 index 09700fda7..000000000 --- a/lib/modules/ble_test/controllers/ble_test_controller.dart +++ /dev/null @@ -1,433 +0,0 @@ -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:ble_service/ble_service.dart'; - -/// BLE测试控制器 -/// 用于测试BLE服务插件的功能 -class BleTestController extends GetxController { - // 设备列表 - final deviceList = [].obs; - - // 扫描状态 - final isScanning = false.obs; - - // 连接状态 - final isConnected = false.obs; - final connectionState = 0.obs; - - // 设备信息 - final connectedDeviceAddress = ''.obs; - final batteryLevel = 0.obs; - final firmwareVersion = ''.obs; - - // 状态消息 - final statusMessage = ''.obs; - - // BLE服务 - final _bleService = BleService(); - - // 订阅 - late StreamSubscription _scanSubscription; - late StreamSubscription _connectionSubscription; - late StreamSubscription _dataSubscription; - late StreamSubscription _statusSubscription; - - @override - void onInit() { - super.onInit(); - _initBleService(); - } - - /// 初始化BLE服务 - Future _initBleService() async { - try { - // 初始化BLE服务 - final result = await _bleService.initialize(); - if (result) { - statusMessage.value = 'BLE服务初始化成功'; - - // 注册回调 - await _bleService.registerCallback(); - - // 订阅扫描结果 - _scanSubscription = _bleService.onScanResults.listen((devices) { - if (devices.isEmpty) { - deviceList.clear(); - return; - } - - bool hasChanges = false; - - for (final newDevice in devices) { - int existingIndex = deviceList.indexWhere((d) => d.id == newDevice.id); - if (existingIndex >= 0) { - BleDevice existing = deviceList[existingIndex]; - if (existing.rssi != newDevice.rssi || existing.name != newDevice.name) { - deviceList[existingIndex] = newDevice; - hasChanges = true; - } - } else { - deviceList.add(newDevice); - hasChanges = true; - } - } - - if (hasChanges) { - deviceList.refresh(); - } - }); - - // 订阅连接状态 - _connectionSubscription = _bleService.onConnectionChanged.listen((state) { - if (state is int) { - connectionState.value = state; - isConnected.value = state == BleConnectionState.connected; - statusMessage.value = '连接状态变更: ${_getStateString(state)}'; - } - }); - - // 订阅数据 - _dataSubscription = _bleService.onDataReceived.listen((data) { - if (data is Map) { - final type = data['type']; - if (type == 'wakeup') { - statusMessage.value = '收到唤醒信号'; - } else if (type == 'audioData') { - statusMessage.value = '收到音频数据: ${(data['data'] as Uint8List).length} 字节'; - } else if (type == 'rawData') { - statusMessage.value = '收到原始数据: ${(data['data'] as Uint8List).length} 字节'; - } - } - }); - - // 订阅状态更新 - _statusSubscription = _bleService.onStatusUpdated.listen((status) { - if (status is Map) { - final type = status['type']; - if (type == 'statusUpdate') { - // 更新状态 - connectionState.value = status['connectionState'] as int? ?? 0; - isConnected.value = connectionState.value == BleConnectionState.connected; - batteryLevel.value = status['batteryLevel'] as int? ?? 0; - firmwareVersion.value = status['firmwareVersion'] as String? ?? ''; - connectedDeviceAddress.value = status['connectedDevice'] as String? ?? ''; - - statusMessage.value = '状态更新: ${_getStateString(connectionState.value)}'; - } else if (type == 'deviceInfo') { - // 设备信息 - final infoType = status['infoType'] as int; - final infoData = status['infoData'] as String; - - statusMessage.value = '设备信息: $infoData'; - - // 处理不同类型的设备信息 - if (infoType == 0x80) { // 版本信息 - firmwareVersion.value = infoData; - } - } - } - }); - - // 获取连接状态 - _updateConnectionState(); - } else { - statusMessage.value = 'BLE服务初始化失败'; - } - } catch (e) { - statusMessage.value = '初始化异常: $e'; - Get.snackbar('初始化失败', '无法初始化BLE服务: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - /// 获取状态字符串 - String _getStateString(int state) { - switch (state) { - case BleConnectionState.disconnected: - return '已断开'; - case BleConnectionState.connecting: - return '连接中'; - case BleConnectionState.connected: - return '已连接'; - case BleConnectionState.disconnecting: - return '断开中'; - case BleConnectionState.error: - return '连接错误'; - default: - return '未知状态($state)'; - } - } - - /// 更新连接状态 - Future _updateConnectionState() async { - connectionState.value = await _bleService.getConnectionState(); - isConnected.value = connectionState.value == BleConnectionState.connected; - - if (isConnected.value) { - connectedDeviceAddress.value = await _bleService.getConnectedDeviceAddress() ?? ''; - batteryLevel.value = await _bleService.getBatteryLevel(); - firmwareVersion.value = await _bleService.getFirmwareVersion() ?? ''; - } - } - - /// 开始扫描设备 - Future startScan() async { - try { - // 清空设备列表 - deviceList.clear(); - - // 设置扫描状态 - isScanning.value = true; - statusMessage.value = '开始扫描...'; - - // 开始扫描 - final result = await _bleService.startScan(timeout: 15000); - - if (!result) { - statusMessage.value = '扫描失败'; - Get.snackbar('扫描失败', '无法启动BLE扫描', - snackPosition: SnackPosition.BOTTOM); - } - } catch (e) { - statusMessage.value = '扫描异常: $e'; - Get.snackbar('扫描错误', '扫描过程中出错: $e', - snackPosition: SnackPosition.BOTTOM); - } finally { - // 5秒后自动停止扫描 - Future.delayed(const Duration(seconds: 15), () { - if (isScanning.value) { - stopScan(); - } - }); - } - } - - /// 停止扫描设备 - Future stopScan() async { - try { - await _bleService.stopScan(); - isScanning.value = false; - statusMessage.value = '扫描已停止'; - } catch (e) { - statusMessage.value = '停止扫描异常: $e'; - Get.snackbar('停止扫描失败', '无法停止BLE扫描: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - /// 连接到设备 - Future connectToDevice(String address) async { - try { - statusMessage.value = '正在连接到 $address...'; - - final result = await _bleService.connect(address); - - if (!result) { - statusMessage.value = '连接失败'; - Get.snackbar('连接失败', '无法连接到设备', - snackPosition: SnackPosition.BOTTOM); - } else { - connectedDeviceAddress.value = address; - statusMessage.value = '连接成功,正在获取设备信息...'; - - // 获取设备信息 - await _bleService.getDeviceVersion(); - await _bleService.getBatteryInfo(); - } - } catch (e) { - statusMessage.value = '连接异常: $e'; - Get.snackbar('连接错误', '连接过程中出错: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - /// 断开设备连接 - Future disconnect() async { - try { - statusMessage.value = '正在断开连接...'; - - await _bleService.disconnect(); - - connectedDeviceAddress.value = ''; - isConnected.value = false; - - statusMessage.value = '已断开连接'; - } catch (e) { - statusMessage.value = '断开连接异常: $e'; - Get.snackbar('断开连接失败', '无法断开设备连接: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - /// 获取设备版本信息 - Future getDeviceVersion() async { - try { - statusMessage.value = '正在获取设备版本...'; - - await _bleService.getDeviceVersion(); - - statusMessage.value = '版本获取命令已发送'; - } catch (e) { - statusMessage.value = '获取版本异常: $e'; - Get.snackbar('获取版本失败', '无法获取设备版本: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - /// 获取电池信息 - Future getBatteryInfo() async { - try { - statusMessage.value = '正在获取电池信息...'; - - await _bleService.getBatteryInfo(); - - statusMessage.value = '电池信息获取命令已发送'; - } catch (e) { - statusMessage.value = '获取电池信息异常: $e'; - Get.snackbar('获取电池信息失败', '无法获取电池信息: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - /// 打开编码器 - Future openEncoder() async { - try { - statusMessage.value = '正在打开编码器...'; - - await _bleService.openEncoder(); - - statusMessage.value = '编码器打开命令已发送'; - } catch (e) { - statusMessage.value = '打开编码器异常: $e'; - Get.snackbar('操作失败', '无法打开编码器: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - /// 打开解码器 - Future openDecoder() async { - try { - statusMessage.value = '正在打开解码器...'; - - await _bleService.openDecoder(); - - statusMessage.value = '解码器打开命令已发送'; - } catch (e) { - statusMessage.value = '打开解码器异常: $e'; - Get.snackbar('操作失败', '无法打开解码器: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - /// 关闭编解码器 - Future closeCodec() async { - try { - statusMessage.value = '正在关闭编解码器...'; - - await _bleService.closeCodec(); - - statusMessage.value = '编解码器关闭命令已发送'; - } catch (e) { - statusMessage.value = '关闭编解码器异常: $e'; - Get.snackbar('操作失败', '无法关闭编解码器: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - /// 播放A2DP - Future playA2DP() async { - try { - statusMessage.value = '正在播放A2DP...'; - - await _bleService.playA2DP(); - - statusMessage.value = 'A2DP播放命令已发送'; - } catch (e) { - statusMessage.value = '播放A2DP异常: $e'; - Get.snackbar('操作失败', '无法播放A2DP: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - /// 音量增加 - Future volumeUp() async { - try { - statusMessage.value = '正在增加音量...'; - - await _bleService.volumeUp(); - - statusMessage.value = '音量增加命令已发送'; - } catch (e) { - statusMessage.value = '增加音量异常: $e'; - Get.snackbar('操作失败', '无法增加音量: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - /// 音量减小 - Future volumeDown() async { - try { - statusMessage.value = '正在减小音量...'; - - await _bleService.volumeDown(); - - statusMessage.value = '音量减小命令已发送'; - } catch (e) { - statusMessage.value = '减小音量异常: $e'; - Get.snackbar('操作失败', '无法减小音量: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - /// 发送音频数据 - Future sendAudioData(Uint8List data) async { - try { - statusMessage.value = '正在发送音频数据...'; - - await _bleService.sendAudioData(data); - - statusMessage.value = '音频数据发送成功: ${data.length} 字节'; - } catch (e) { - statusMessage.value = '发送音频数据异常: $e'; - Get.snackbar('发送失败', '无法发送音频数据: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - /// 清理资源 - Future cleanup() async { - try { - await _bleService.cleanup(); - } catch (e) { - Get.snackbar('清理失败', '无法清理BLE资源: $e', - snackPosition: SnackPosition.BOTTOM); - } - } - - @override - void onClose() { - // 取消所有订阅 - _scanSubscription.cancel(); - _connectionSubscription.cancel(); - _dataSubscription.cancel(); - _statusSubscription.cancel(); - - // 停止扫描 - if (isScanning.value) { - stopScan(); - } - - // 断开连接 - if (isConnected.value) { - disconnect(); - } - - // 清理资源 - cleanup(); - - super.onClose(); - } -} \ No newline at end of file diff --git a/lib/modules/ble_test/views/ble_test_view.dart b/lib/modules/ble_test/views/ble_test_view.dart deleted file mode 100644 index 58ed92eae..000000000 --- a/lib/modules/ble_test/views/ble_test_view.dart +++ /dev/null @@ -1,279 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import '../controllers/ble_test_controller.dart'; - -class BleTestView extends GetView { - const BleTestView({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('BLE测试'), - actions: [ - Obx(() => IconButton( - icon: const Icon(Icons.refresh), - onPressed: controller.isScanning.value ? null : controller.startScan, - )), - ], - ), - body: Column( - children: [ - // 状态消息 - 使用AnimatedContainer让过渡更平滑 - Obx(() => AnimatedContainer( - duration: const Duration(milliseconds: 300), - padding: const EdgeInsets.all(8), - width: double.infinity, - color: Colors.grey[200], - child: Text( - controller.statusMessage.value, - style: const TextStyle(fontSize: 14), - ), - )), - - // 根据连接状态显示不同的内容 - Expanded( - child: Obx(() { - // 保存连接状态到局部变量,避免多次访问反应式变量 - final isConnected = controller.isConnected.value; - return isConnected ? _buildConnectedView() : _buildScanView(); - }), - ), - ], - ), - ); - } - - // 扫描视图 - Widget _buildScanView() { - return Column( - children: [ - // 扫描控制 - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Obx(() { - final isScanning = controller.isScanning.value; - return ElevatedButton( - onPressed: isScanning ? null : controller.startScan, - child: Text(isScanning ? '扫描中...' : '开始扫描'), - ); - }), - Obx(() => ElevatedButton( - onPressed: controller.isScanning.value - ? controller.stopScan - : null, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - ), - child: const Text('停止扫描'), - )), - ], - ), - ), - - // 设备列表 - 使用RepaintBoundary提高性能 - Expanded( - child: RepaintBoundary( - child: Obx(() { - if (controller.deviceList.isEmpty) { - return const Center( - child: Text('没有找到设备,请开始扫描'), - ); - } - - // 仅在需要时创建列表视图 - return ListView.builder( - // 使用常量key避免整体重建 - key: const ValueKey('device_list'), - itemCount: controller.deviceList.length, - itemBuilder: (context, index) { - final device = controller.deviceList[index]; - return Card( - margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: ListTile( - title: Text(device.name), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('ID: ${device.id}'), - if (device.uuids.isNotEmpty) - Text( - 'UUID: ${device.uuids.length > 0 ? device.uuids.first : "无"}', - style: const TextStyle(fontSize: 12), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - if (device.uuids.length > 1) - Text( - '其他UUID: ${device.uuids.length - 1}个', - style: const TextStyle(fontSize: 12), - overflow: TextOverflow.ellipsis, - ), - ], - ), - trailing: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('RSSI: ${device.rssi}'), - const SizedBox(height: 4), - const Icon(Icons.bluetooth, size: 16), - ], - ), - onTap: () => controller.connectToDevice(device.id), - ), - ); - }, - ); - }), - ), - ), - ], - ); - } - - // 已连接设备视图 - Widget _buildConnectedView() { - return SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 设备信息卡片 - Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Obx(() => Text( - '设备地址: ${controller.connectedDeviceAddress.value}', - style: const TextStyle(fontWeight: FontWeight.bold), - )), - const SizedBox(height: 8), - Obx(() => Text('电池电量: ${controller.batteryLevel.value}%')), - const SizedBox(height: 4), - Obx(() => Text('固件版本: ${controller.firmwareVersion.value}')), - const SizedBox(height: 16), - Wrap( - spacing: 8, // 水平间距 - runSpacing: 8, // 垂直间距 - alignment: WrapAlignment.spaceBetween, - children: [ - SizedBox( - width: 110, - child: ElevatedButton( - onPressed: controller.getDeviceVersion, - child: const Text('获取版本'), - ), - ), - SizedBox( - width: 110, - child: ElevatedButton( - onPressed: controller.getBatteryInfo, - child: const Text('获取电量'), - ), - ), - SizedBox( - width: 110, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - ), - onPressed: controller.disconnect, - child: const Text('断开连接'), - ), - ), - ], - ), - ], - ), - ), - ), - - const SizedBox(height: 24), - - // 音频控制 - const Text('音频控制', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), - const SizedBox(height: 16), - - Wrap( - spacing: 8, - runSpacing: 8, - alignment: WrapAlignment.start, - children: [ - SizedBox( - width: 140, - child: ElevatedButton( - onPressed: controller.openEncoder, - child: const Text('打开编码器'), - ), - ), - SizedBox( - width: 140, - child: ElevatedButton( - onPressed: controller.openDecoder, - child: const Text('打开解码器'), - ), - ), - ], - ), - - const SizedBox(height: 16), - - Wrap( - spacing: 8, - runSpacing: 8, - alignment: WrapAlignment.start, - children: [ - SizedBox( - width: 140, - child: ElevatedButton( - onPressed: controller.closeCodec, - child: const Text('关闭编解码器'), - ), - ), - SizedBox( - width: 140, - child: ElevatedButton( - onPressed: controller.playA2DP, - child: const Text('播放A2DP'), - ), - ), - ], - ), - - const SizedBox(height: 24), - - // 音量控制 - const Text('音量控制', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), - const SizedBox(height: 16), - - Wrap( - spacing: 8, - runSpacing: 8, - alignment: WrapAlignment.start, - children: [ - SizedBox( - width: 140, - child: ElevatedButton( - onPressed: controller.volumeUp, - child: const Text('音量+'), - ), - ), - SizedBox( - width: 140, - child: ElevatedButton( - onPressed: controller.volumeDown, - child: const Text('音量-'), - ), - ), - ], - ), - ], - ), - ); - } -} \ No newline at end of file diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index 15396b6c4..7955486b1 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -19,8 +19,6 @@ import '../../../data/services/tts_service.dart'; import '../../../data/providers/agent_provider.dart'; import '../../../data/models/agent.dart'; import '../../../data/services/asr_service.dart'; -import '../../../data/models/events/voice_interaction_event.dart'; -import '../../../data/services/voice_interaction_service.dart'; import '../../../data/services/open_ai_service_adapter.dart'; class ChatController extends GetxController { @@ -28,8 +26,7 @@ class ChatController extends GetxController { late AiService _aiService; late TtsService _ttsService; final AsrService _asrService = Get.find(); - final VoiceInteractionService _voiceInteractionService = - Get.find(); + late SpotifService _spotifService; // 添加ScrollController用于控制列表滚动 @@ -187,8 +184,6 @@ class ChatController extends GetxController { // 自动停止计时器会在startVoiceRecognition中启动 } - // 订阅VoiceInteractionService事件 - _subscribeToVoiceInteractionEvents(); _getLocation(); @@ -216,73 +211,7 @@ class ChatController extends GetxController { _amapFlutter.startLocation(); } - /// 订阅VoiceInteractionService事件 - void _subscribeToVoiceInteractionEvents() { - if (_voiceInteractionService.eventStream != null) { - _voiceInteractionSubscription = - _voiceInteractionService.eventStream.listen((event) { - if (event is RecognitionStartedEvent) { - // 收到recognitionStarted事件时,停止当前的AI交互、录音和TTS - _stopCurrentInteraction(); - } else if (event is ChatHistoryEvent) { - // 收到聊天历史事件,添加到聊天记录 - _processChatHistoryEvent(event); - } - }); - } - } - - /// 处理聊天历史事件 - void _processChatHistoryEvent(ChatHistoryEvent event) { - Logger.info('收到后台聊天历史事件: agentId=${event.agentId}'); - - // 如果消息不是空的且不是当前Agent的,不处理 - if (event.userMessage.isEmpty || event.agentId != agent.id) { - return; - } - - // 添加用户消息 - final userMsg = Message( - role: 'user', - content: event.userMessage, - timestamp: DateTime.fromMillisecondsSinceEpoch(event.timestamp), - ); - // 添加助手消息 - final assistantMsg = Message( - role: 'assistant', - content: event.assistantMessage, - timestamp: - DateTime.fromMillisecondsSinceEpoch(event.timestamp + 1), // 助手消息稍后一毫秒 - ); - - // 防止消息重复 - if (!_isDuplicateMessage(userMsg) && !_isDuplicateMessage(assistantMsg)) { - messages.add(userMsg); - messages.add(assistantMsg); - } - WidgetsBinding.instance.addPostFrameCallback((_) { - _scrollToBottom(); - }); - } - - /// 停止当前交互 - void _stopCurrentInteraction() { - Logger.info('收到后台语音识别开始事件,停止当前AI交互'); - - // 停止当前AI交互 - if (isSending.value || _aiResponseSubscription != null) { - _interruptCurrentResponse("收到后台语音识别开始事件,中断当前响应"); - } - - // 停止TTS - _ttsService.stop(); - - // 停止语音识别 - if (isVoiceRecognitionActive.value) { - stopVoiceRecognition(); - } - } @override void onClose() { @@ -366,7 +295,6 @@ class ChatController extends GetxController { Future sendMessage(String text) async { if (text.trim().isEmpty) return; - _voiceInteractionService.pauseVoiceInteraction(); // 中断当前响应 _interruptCurrentResponse("发送新消息,中断当前响应"); @@ -630,7 +558,7 @@ class ChatController extends GetxController { Timer(const Duration(milliseconds: 260), () async { if (isDebounce) return; - _voiceInteractionService.pauseVoiceInteraction(); + // 中断当前响应 _interruptCurrentResponse("发送新消息,中断当前响应"); try { @@ -691,7 +619,7 @@ class ChatController extends GetxController { Future startVoiceRecognition() async { if (isVoiceRecognitionActive.value) return; - _voiceInteractionService.pauseVoiceInteraction(); + try { // 启动连续语音识别 // 注意:在UI更新后,使用模拟波形动画替代实际麦克风输入的波形数据 diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart index 84358806c..78902c2d9 100644 --- a/lib/modules/home/controllers/home_controller.dart +++ b/lib/modules/home/controllers/home_controller.dart @@ -4,9 +4,10 @@ import '../../../routes/app_routes.dart'; import '../../../core/utils/logger.dart'; import 'package:flutter/material.dart'; import '../../explore/controllers/explore_controller.dart'; -import 'package:classic_bluetooth/classic_bluetooth.dart'; +import 'package:classic_bluetooth/classic_bluetooth.dart' show BluetoothDevice; +import '../../../data/services/bluetooth_service.dart'; -// 连接状态枚举 +/// 连接状态枚举 enum ConnectionStatus { disconnected, // 未连接 connecting, // 连接中 @@ -14,199 +15,177 @@ enum ConnectionStatus { failed // 连接失败 } +/// 主页控制器,管理设备连接状态和信息展示 class HomeController extends GetxController { static HomeController get to => Get.find(); + + // 依赖的控制器 final ExploreController exploreController = Get.find(); - // 连接状态 + // 连接状态相关 final _connectionStatus = ConnectionStatus.disconnected.obs; - ConnectionStatus get connectionStatus => _connectionStatus.value; - - // 电池状态(模拟数据) - final _deviceBattery = Rx(null); - int? get deviceBattery => _deviceBattery.value; - - // 固件版本(模拟数据) - final String firmwareVersion = 'v2.4.1'; - - // 连接错误信息 final _connectionErrorMsg = ''.obs; - String get connectionErrorMsg => _connectionErrorMsg.value; - - // 蓝牙状态和设备信息 final _bluetoothState = 'unknown'.obs; + + // Getter方法 + ConnectionStatus get connectionStatus => _connectionStatus.value; + String get connectionErrorMsg => _connectionErrorMsg.value; String get bluetoothState => _bluetoothState.value; + BluetoothDevice? get currentDevice => _bluetoothService.currentDevice; + DeviceInfo? get deviceInfo => _bluetoothService.deviceInfo; + bool get isConnected => currentDevice != null; - final Rx _currentDevice = Rx(null); - BluetoothDevice? get currentDevice => _currentDevice.value; + // 设备详细信息的getter,从deviceInfo获取数据 + int get leftEarBattery => deviceInfo?.leftEarBattery ?? 0; + int get rightEarBattery => deviceInfo?.rightEarBattery ?? 0; + int get caseBattery => deviceInfo?.caseBattery ?? 0; + bool get leftEarCharging => deviceInfo?.leftEarCharging ?? false; + bool get rightEarCharging => deviceInfo?.rightEarCharging ?? false; + bool get caseCharging => deviceInfo?.caseCharging ?? false; + String get firmwareVersion => deviceInfo?.firmwareVersion ?? ''; + String get productId => deviceInfo?.productId ?? ''; + String get colorId => deviceInfo?.colorId ?? ''; - // 蓝牙事件订阅 - StreamSubscription? _bluetoothSubscription; + // 监听和订阅 + Worker? _deviceInfoWorker; + Worker? _bluetoothStateWorker; + Worker? _deviceWorker; - // 蓝牙是否已连接 - bool get isConnected => currentDevice != null; + // 蓝牙服务 + final _bluetoothService = Get.find(); @override void onInit() { super.onInit(); - - // 初始化 - _initBluetooth(); - - // 电池模拟更新 - _simulateBatteryUpdate(); + _initWithCachedInfo(); } @override void onClose() { - _bluetoothSubscription?.cancel(); + _deviceInfoWorker?.dispose(); + _bluetoothStateWorker?.dispose(); + _deviceWorker?.dispose(); super.onClose(); } - // 初始化蓝牙 - Future _initBluetooth() async { - try { - _connectionStatus.value = ConnectionStatus.connecting; - - // 检查蓝牙状态 - final isEnabled = await ClassicBluetooth.isBluetoothEnabled(); - _bluetoothState.value = isEnabled ? 'on' : 'off'; - - if (!isEnabled) { - _connectionStatus.value = ConnectionStatus.disconnected; + /// 使用缓存的蓝牙设备信息初始化 + void _initWithCachedInfo() { + // 使用蓝牙服务中的当前状态 + _bluetoothState.value = _bluetoothService.bluetoothState; + + // 根据当前设备状态设置连接状态 + if (currentDevice != null) { + _connectionStatus.value = ConnectionStatus.connected; + _connectionErrorMsg.value = ''; + Logger.info('主页加载缓存设备信息: ${currentDevice!.name}'); + } else { + _connectionStatus.value = ConnectionStatus.disconnected; + if (_bluetoothState.value == 'off') { _connectionErrorMsg.value = '蓝牙未开启'; - return; + } else { + _connectionErrorMsg.value = '未连接到设备'; } - - // 设置蓝牙事件监听 - _setupBluetoothListener(); - - // 获取已连接设备 - await refreshDeviceConnection(); - - } catch (e) { - Logger.error('初始化蓝牙失败: $e'); - _connectionStatus.value = ConnectionStatus.failed; - _connectionErrorMsg.value = e.toString(); } + + // 设置监听器监听蓝牙服务状态变化 + _setupStateListeners(); } - - // 设置蓝牙事件监听 - void _setupBluetoothListener() { - _bluetoothSubscription?.cancel(); + + /// 设置状态监听器 + void _setupStateListeners() { + // 设备信息监听 - 设备信息更新时自动触发UI更新 + _deviceInfoWorker = ever(_bluetoothService.deviceInfo.obs, (DeviceInfo? info) { + if (info != null) { + Logger.info('主页更新设备信息: ${info.toString()}'); + } else { + Logger.info('主页清空设备信息'); + } + update(); + }); - _bluetoothSubscription = ClassicBluetooth.events.listen((event) { - if (event.type == 'stateChanged' && event.state != null) { - _bluetoothState.value = event.state!; - - // 如果蓝牙关闭,清空设备信息 - if (event.state == 'off') { - _currentDevice.value = null; - _connectionErrorMsg.value = '蓝牙未开启'; - } - - _updateConnectionStatus(); - } else if (event.type == 'deviceConnected' && event.device != null) { - final newDevice = event.device!; - - // 更新当前设备 - _currentDevice.value = newDevice; - _connectionErrorMsg.value = ''; - _updateConnectionStatus(); - } else if (event.type == 'deviceDisconnected' && event.device != null) { - final disconnectedDevice = event.device!; - - // 如果断开的是当前设备,清除当前设备 - if (_currentDevice.value?.address == disconnectedDevice.address) { - _currentDevice.value = null; - _connectionErrorMsg.value = '设备已断开连接'; + // 蓝牙状态监听 + _bluetoothStateWorker = ever(_bluetoothService.bluetoothState.obs, (String state) { + _bluetoothState.value = state; + + // 如果蓝牙关闭,更新错误信息 + if (state == 'off') { + _connectionErrorMsg.value = '蓝牙未开启'; + _connectionStatus.value = ConnectionStatus.disconnected; + } + + update(); + }); + + // 设备连接状态监听 - 直接监听设备变化 + _deviceWorker = ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) { + if (device != null) { + // 设备已连接 + if (_connectionStatus.value != ConnectionStatus.connected) { + _connectionStatus.value = ConnectionStatus.connected; + _connectionErrorMsg.value = ''; + + Logger.info('主页检测到设备已连接: ${device.name}'); } + } else if (_connectionStatus.value == ConnectionStatus.connected) { + // 设备已断开 + _connectionStatus.value = ConnectionStatus.disconnected; + _connectionErrorMsg.value = '设备已断开连接'; - _updateConnectionStatus(); + Logger.info('主页检测到设备已断开连接'); } - }, onError: (error) { - Logger.error('蓝牙事件流错误: $error'); + + update(); }); } - - // 更新连接状态 - void _updateConnectionStatus() { - if (bluetoothState == 'off') { - _connectionStatus.value = ConnectionStatus.disconnected; - _deviceBattery.value = null; - update(); - return; - } - - if (currentDevice != null) { + + /// 刷新设备连接状态 + /// 注意:此方法不会主动刷新设备,只更新UI状态 + Future refreshDeviceConnection() async { + // 更新当前状态以反映UI变化 + bool hasDevice = currentDevice != null; + + if (hasDevice) { _connectionStatus.value = ConnectionStatus.connected; + _connectionErrorMsg.value = ''; + Logger.info('使用缓存设备信息: ${currentDevice!.name}'); } else { + if (_bluetoothState.value == 'off') { + _connectionErrorMsg.value = '蓝牙未开启'; + } else { + _connectionErrorMsg.value = '未连接到设备'; + } _connectionStatus.value = ConnectionStatus.disconnected; - _deviceBattery.value = null; } + // 刷新UI update(); } - // 刷新设备连接 - Future refreshDeviceConnection() async { - try { - _connectionStatus.value = ConnectionStatus.connecting; - update(); - - final devices = await ClassicBluetooth.getConnectedDevices(); - - if (devices.isNotEmpty) { - _currentDevice.value = devices.first; - _connectionErrorMsg.value = ''; - } else { - _currentDevice.value = null; - _connectionErrorMsg.value = '未找到已连接的设备'; - } - } catch (e) { - Logger.error('刷新设备连接失败: $e'); - _connectionErrorMsg.value = '无法刷新设备连接'; - } finally { - _updateConnectionStatus(); - } - } - - // 电池电量模拟更新 - void _simulateBatteryUpdate() { - Future.delayed(const Duration(seconds: 2), () { - if (isConnected) { - final timestamp = DateTime.now().millisecondsSinceEpoch; - _deviceBattery.value = 50 + (timestamp % 50); // 50-99的随机值 - } else { - _deviceBattery.value = null; - } - - // 递归调用实现定时更新 - _simulateBatteryUpdate(); - }); - } - - // 重新扫描设备 + // 导航和功能方法 + + /// 重新扫描设备 void rescanDevices() { Get.toNamed(Routes.pairing); } - // 打开翻译功能 + /// 打开翻译功能 void openTranslation() { Get.toNamed(Routes.translation); } - // 打开AI伴侣功能 + /// 打开AI伴侣功能 void openAIGirlfriend() { - exploreController.onAIFeatureTap(2); //进入AI女友页面 + exploreController.onAIFeatureTap(2); } - // 展示AI功能 + /// 展示AI功能 void exploreAIFeatures() { Get.toNamed(Routes.chat); } - // 获取连接状态对应的颜色 + // UI辅助方法 + + /// 获取连接状态对应的颜色 Color getConnectionStatusColor() { switch (_connectionStatus.value) { case ConnectionStatus.connected: @@ -221,51 +200,110 @@ class HomeController extends GetxController { } } - // 获取连接状态对应的文本 + /// 获取连接状态对应的文本 String getConnectionStatusText() { - // 如果蓝牙关闭,优先显示蓝牙状态 if (bluetoothState == 'off') { - return 'bluetoothOff'.tr; // 蓝牙已关闭 + return 'bluetoothOff'.tr; } switch (_connectionStatus.value) { case ConnectionStatus.connected: - return 'connected'.tr; // 已连接 + return 'connected'.tr; case ConnectionStatus.connecting: - return 'connecting'.tr; // 连接中... + return 'connecting'.tr; case ConnectionStatus.failed: - return 'connectionFailure'.tr; // 连接失败 + return 'connectionFailure'.tr; case ConnectionStatus.disconnected: default: - return 'unconnected'.tr; // 未连接 + return 'unconnected'.tr; } } - // 获取设备名称 + /// 获取设备名称 String getDeviceName() { - return currentDevice?.name ?? 'unknownEquipment'.tr; // 未知设备 + return currentDevice?.name ?? 'unknownEquipment'.tr; } - // 获取信号强度(模拟数据) + /// 获取信号强度(模拟数据) String getSignalStrength() { if (!isConnected) { - return 'zeroSignal'.tr; // 无信号 + return 'zeroSignal'.tr; } - // 简单模拟信号强度 final random = DateTime.now().millisecondsSinceEpoch % 3; switch (random) { case 0: - return 'good'.tr; // 良好 + return 'good'.tr; case 1: - return 'fine'.tr; // 很好 + return 'fine'.tr; default: - return 'excellent'.tr; // 极佳 + return 'excellent'.tr; } } - // 获取设备ID + /// 获取设备ID String getDeviceId() { - return currentDevice?.address ?? 'unknown'.tr; // 未知 + return deviceInfo?.address ?? currentDevice?.address ?? 'unknown'.tr; + } + + /// 获取设备电量文本 + String getBatteryText() { + return deviceInfo?.batteryLevel != null && deviceInfo!.batteryLevel > 0 + ? '${deviceInfo!.batteryLevel}%' + : 'unknown'.tr; + } + + /// 获取耳机左右电量文本 + String getEarbudsText() { + if (deviceInfo == null || (deviceInfo!.leftEarBattery <= 0 && deviceInfo!.rightEarBattery <= 0)) { + return 'unknown'.tr; + } + + String leftStatus = deviceInfo!.leftEarBattery > 0 ? '${deviceInfo!.leftEarBattery}%' : 'N/A'; + String rightStatus = deviceInfo!.rightEarBattery > 0 ? '${deviceInfo!.rightEarBattery}%' : 'N/A'; + + leftStatus = deviceInfo!.leftEarCharging ? '$leftStatus ⚡' : leftStatus; + rightStatus = deviceInfo!.rightEarCharging ? '$rightStatus ⚡' : rightStatus; + + return '左耳: $leftStatus / 右耳: $rightStatus'; + } + + /// 获取充电仓电量文本 + String getCaseText() { + if (deviceInfo == null || deviceInfo!.caseBattery <= 0) { + return 'unknown'.tr; + } + + String caseStatus = '${deviceInfo!.caseBattery}%'; + if (deviceInfo!.caseCharging) { + caseStatus = '$caseStatus ⚡'; + } + + return '充电仓: $caseStatus'; + } + + /// 获取充电状态文本 + String getChargingText() { + if (deviceInfo == null) { + return 'unknown'.tr; + } + + bool isCharging = deviceInfo!.leftEarCharging || + deviceInfo!.rightEarCharging || + deviceInfo!.caseCharging; + + return isCharging ? 'charging'.tr : 'notCharging'.tr; + } + + /// 获取设备版本文本 + String getVersionText() { + return deviceInfo?.firmwareVersion.isNotEmpty == true ? + deviceInfo!.firmwareVersion : 'unknown'.tr; + } + + /// 获取设备类型文本 + String getProductTypeText() { + return deviceInfo?.productId.isNotEmpty == true ? + "型号: ${deviceInfo!.productId}" : 'unknown'.tr; } } diff --git a/lib/modules/home/views/home_view.dart b/lib/modules/home/views/home_view.dart index fee687b34..0f4de7a89 100644 --- a/lib/modules/home/views/home_view.dart +++ b/lib/modules/home/views/home_view.dart @@ -212,50 +212,7 @@ class HomeView extends GetView { child: Column( children: [ // 状态标题行 - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'deviceStatus'.tr, // 设备状态 - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w500, - color: isDarkMode ? Colors.grey[300] : Colors.grey[700], - ), - ), - Obx(() => Row( - children: [ - Container( - width: 8.w, - height: 8.w, - decoration: BoxDecoration( - color: controller.getConnectionStatusColor(), - shape: BoxShape.circle, - ), - ), - SizedBox(width: 4.w), - Text( - controller.getConnectionStatusText(), - style: TextStyle( - fontSize: 12.sp, - color: controller.getConnectionStatusColor(), - ), - ), - // 添加刷新按钮 - SizedBox(width: 8.w), - InkWell( - onTap: () => controller.refreshDeviceConnection(), - child: Icon( - Icons.refresh, - size: 16.sp, - color: - isDarkMode ? Colors.grey[400] : Colors.grey[600], - ), - ), - ], - )), - ], - ), + _buildDeviceStatusHeader(isDarkMode), // 设备信息 SizedBox(height: 12.h), @@ -263,233 +220,24 @@ class HomeView extends GetView { // 如果连接失败且有错误信息,显示错误提示 if (controller.connectionStatus == ConnectionStatus.failed && controller.connectionErrorMsg.isNotEmpty) { - return Column( - children: [ - Container( - padding: EdgeInsets.all(8.w), - decoration: BoxDecoration( - color: isDarkMode - ? Colors.red[900]!.withOpacity(0.2) - : Colors.red[50], - borderRadius: BorderRadius.circular(8.r), - border: Border.all( - color: - isDarkMode ? Colors.red[800]! : Colors.red[200]!), - ), - child: Row( - children: [ - Icon( - Icons.error_outline, - color: Colors.red, - size: 16.sp, - ), - SizedBox(width: 8.w), - Expanded( - child: Text( - controller.connectionErrorMsg, - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode - ? Colors.red[300] - : Colors.red[800], - ), - ), - ), - ], - ), - ), - SizedBox(height: 12.h), - // 连接按钮 - ElevatedButton( - onPressed: controller.rescanDevices, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - minimumSize: Size(double.infinity, 36.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - ), - ), - child: Text('reconnect'.tr), // 重新连接 - ), - ], - ); + return _buildConnectionErrorSection(isDarkMode); } // 如果正在连接中,显示连接中提示 if (controller.connectionStatus == ConnectionStatus.connecting) { - return Container( - padding: EdgeInsets.symmetric(vertical: 16.h), - child: Center( - child: Column( - children: [ - SizedBox( - width: 24.w, - height: 24.w, - child: CircularProgressIndicator( - strokeWidth: 2.w, - valueColor: - AlwaysStoppedAnimation(Colors.blue), - ), - ), - SizedBox(height: 8.h), - Text( - 'connectingDevice'.tr, // 正在连接设备... - style: TextStyle( - fontSize: 12.sp, - color: Colors.blue, - ), - ), - ], - ), - ), - ); + return _buildConnectingIndicator(); } return controller.isConnected - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - '${'connectedTo'.tr}:${controller.getDeviceName()}', // 已连接到 - style: TextStyle( - fontSize: 12.sp, - fontWeight: FontWeight.w500, - color: isDarkMode - ? Colors.grey[300] - : Colors.grey[700], - ), - overflow: TextOverflow.ellipsis, - ), - ), - Container( - padding: EdgeInsets.symmetric( - horizontal: 6.w, vertical: 2.h), - decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.1), - borderRadius: BorderRadius.circular(4.r), - ), - child: Text( - '${'signal'.tr}: ${controller.getSignalStrength()}', // 信号 - style: TextStyle( - fontSize: 10.sp, - color: Colors.blue[700], - ), - ), - ), - ], - ), - SizedBox(height: 8.h), - Container( - padding: EdgeInsets.symmetric( - horizontal: 8.w, vertical: 6.h), - decoration: BoxDecoration( - color: - isDarkMode ? Colors.grey[900] : Colors.grey[50], - borderRadius: BorderRadius.circular(8.r), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${'deviceId'.tr}:${controller.getDeviceId()}', // 设备ID - style: TextStyle( - fontSize: 10.sp, - color: isDarkMode - ? Colors.grey[400] - : Colors.grey[500], - ), - ), - SizedBox(height: 4.h), - Text( - '${'firmwareVersion'.tr}:${controller.firmwareVersion}', // 固件版本 - style: TextStyle( - fontSize: 10.sp, - color: isDarkMode - ? Colors.grey[400] - : Colors.grey[500], - ), - ), - SizedBox(height: 4.h), - // 显示设备状态(已连接/已配对) - Row( - children: [ - Container( - padding: EdgeInsets.symmetric( - horizontal: 6.w, vertical: 2.h), - decoration: BoxDecoration( - color: controller - .getConnectionStatusColor() - .withOpacity(0.1), - borderRadius: BorderRadius.circular(4.r), - ), - child: Text( - controller.getConnectionStatusText(), - style: TextStyle( - fontSize: 10.sp, - color: - controller.getConnectionStatusColor(), - ), - ), - ), - ], - ), - ], - ), - ), - ], - ) - : Container( - padding: EdgeInsets.symmetric(vertical: 8.h), - alignment: Alignment.center, - decoration: BoxDecoration( - color: isDarkMode ? Colors.grey[900] : Colors.grey[100], - borderRadius: BorderRadius.circular(8.r), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.bluetooth_disabled, - size: 14.sp, - color: - isDarkMode ? Colors.grey[400] : Colors.grey[700], - ), - SizedBox(width: 4.w), - Text( - 'disconnectedDevice'.tr, // 未连接设备,点击下方按钮连接 - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode - ? Colors.grey[400] - : Colors.grey[700], - ), - ), - ], - ), - ); + ? _buildConnectedDeviceInfo(isDarkMode) + : _buildDisconnectedInfo(isDarkMode); }), // 连接按钮(仅在未连接时显示) SizedBox(height: 12.h), Obx(() { if (controller.connectionStatus == ConnectionStatus.disconnected) { - return ElevatedButton( - onPressed: controller.rescanDevices, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - minimumSize: Size(double.infinity, 36.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - ), - ), - child: Text('connectDevice'.tr), // 连接设备 - ); + return _buildConnectButton(); } else { return const SizedBox(); } @@ -499,6 +247,521 @@ class HomeView extends GetView { ); } + // 设备状态头部 + Widget _buildDeviceStatusHeader(bool isDarkMode) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Icon( + Icons.headset_outlined, + size: 18.sp, + color: isDarkMode ? Colors.grey[300] : Colors.grey[700], + ), + SizedBox(width: 8.w), + Text( + 'deviceStatus'.tr, // 设备状态 + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.grey[300] : Colors.grey[700], + ), + ), + ], + ), + Obx(() => Row( + children: [ + Container( + width: 8.w, + height: 8.w, + decoration: BoxDecoration( + color: controller.getConnectionStatusColor(), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: controller.getConnectionStatusColor().withOpacity(0.3), + blurRadius: 4, + spreadRadius: 1, + ), + ], + ), + ), + SizedBox(width: 4.w), + Text( + controller.getConnectionStatusText(), + style: TextStyle( + fontSize: 12.sp, + color: controller.getConnectionStatusColor(), + ), + ), + // 添加刷新按钮 + SizedBox(width: 8.w), + InkWell( + onTap: () => controller.refreshDeviceConnection(), + borderRadius: BorderRadius.circular(12.r), + child: Container( + padding: EdgeInsets.all(4.w), + decoration: BoxDecoration( + color: isDarkMode ? Colors.grey[800] : Colors.grey[200], + shape: BoxShape.circle, + ), + child: Icon( + Icons.refresh, + size: 14.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], + ), + ), + ), + ], + )), + ], + ); + } + + // 连接错误部分 + Widget _buildConnectionErrorSection(bool isDarkMode) { + return Column( + children: [ + Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.red[900]!.withOpacity(0.2) + : Colors.red[50], + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: isDarkMode ? Colors.red[800]! : Colors.red[200]!), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.error_outline, + color: Colors.red, + size: 18.sp, + ), + SizedBox(width: 8.w), + Expanded( + child: Text( + controller.connectionErrorMsg, + style: TextStyle( + fontSize: 12.sp, + height: 1.4, + color: isDarkMode ? Colors.red[300] : Colors.red[800], + ), + ), + ), + ], + ), + ), + SizedBox(height: 12.h), + // 连接按钮 + ElevatedButton( + onPressed: controller.rescanDevices, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + minimumSize: Size(double.infinity, 40.h), + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.r), + ), + ), + child: Text( + 'reconnect'.tr, // 重新连接 + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ); + } + + // 连接中指示器 + Widget _buildConnectingIndicator() { + return Container( + padding: EdgeInsets.symmetric(vertical: 20.h), + child: Center( + child: Column( + children: [ + SizedBox( + width: 30.w, + height: 30.w, + child: CircularProgressIndicator( + strokeWidth: 2.5.w, + valueColor: AlwaysStoppedAnimation(Colors.blue), + ), + ), + SizedBox(height: 12.h), + Text( + 'connectingDevice'.tr, // 正在连接设备... + style: TextStyle( + fontSize: 13.sp, + color: Colors.blue, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ); + } + + // 已连接设备信息 + Widget _buildConnectedDeviceInfo(bool isDarkMode) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 电池状态网格布局 + _buildBatteryInfoGrid(isDarkMode), + ], + ); + } + + // 电池信息网格布局 + Widget _buildBatteryInfoGrid(bool isDarkMode) { + return Obx(() { + // 获取各部分电池电量 + final leftBattery = controller.leftEarBattery; + final rightBattery = controller.rightEarBattery; + final caseBattery = controller.caseBattery; + + // 获取充电状态 + final leftEarCharging = controller.leftEarCharging; + final rightEarCharging = controller.rightEarCharging; + final caseCharging = controller.caseCharging; + + return GridView.count( + crossAxisCount: 3, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + crossAxisSpacing: 12.w, + childAspectRatio: 0.85, + children: [ + // 左耳机 + _buildBatteryGridItem( + icon: Icons.headphones, + label: '左耳机', + batteryLevel: leftBattery, + isCharging: leftEarCharging, + isDarkMode: isDarkMode, + ), + + // 右耳机 + _buildBatteryGridItem( + icon: Icons.headphones, + label: '右耳机', + batteryLevel: rightBattery, + isCharging: rightEarCharging, + isDarkMode: isDarkMode, + ), + + // 充电盒 + _buildBatteryGridItem( + icon: Icons.charging_station, + label: '充电盒', + batteryLevel: caseBattery, + isCharging: caseCharging, + isDarkMode: isDarkMode, + ), + ], + ); + }); + } + + // 单个电池网格项 + Widget _buildBatteryGridItem({ + required IconData icon, + required String label, + required int batteryLevel, + required bool isDarkMode, + bool isCharging = false, + }) { + // 获取电池图标和颜色 + IconData batteryIcon; + Color batteryColor; + + if (batteryLevel > 75) { + batteryIcon = Icons.battery_full; + batteryColor = Colors.green[600]!; + } else if (batteryLevel > 50) { + batteryIcon = Icons.battery_6_bar; + batteryColor = Colors.green[600]!; + } else if (batteryLevel > 25) { + batteryIcon = Icons.battery_4_bar; + batteryColor = Colors.yellow[600]!; + } else if (batteryLevel > 10) { + batteryIcon = Icons.battery_3_bar; + batteryColor = Colors.orange[600]!; + } else { + batteryIcon = Icons.battery_alert; + batteryColor = Colors.red[600]!; + } + + return Container( + decoration: BoxDecoration( + color: isDarkMode ? Colors.grey[900] : Colors.grey[100], + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: isDarkMode ? + Colors.black.withOpacity(0.1) : + Colors.grey.withOpacity(0.1), + blurRadius: 4, + spreadRadius: 0, + offset: Offset(0, 2), + ), + ], + ), + padding: EdgeInsets.all(10.w), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 设备图标 + Container( + padding: EdgeInsets.all(8.w), + decoration: BoxDecoration( + shape: BoxShape.circle, + ), + child: Icon( + icon, + size: 24.sp, + color: isDarkMode ? Colors.grey[300] : Colors.grey[800], + ), + ), + SizedBox(height: 6.h), + + // 标签 + Text( + label, + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[500], + fontWeight: FontWeight.normal, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 6.h), + + // 电池指示器 + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + batteryIcon, + size: 14.sp, + color: batteryColor, + ), + SizedBox(width: 4.w), + Text( + '$batteryLevel%', + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + color: batteryColor, + ), + ), + if (isCharging) ...[ + SizedBox(width: 2.w), + Icon( + Icons.bolt, + size: 12.sp, + color: Colors.green[600], + ), + ], + ], + ), + ], + ), + ); + } + + // 设备详情卡片 + Widget _buildDeviceDetailsCard(bool isDarkMode) { + return Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: isDarkMode ? Colors.grey[900] : Colors.grey[50], + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: isDarkMode ? Colors.grey[800]! : Colors.grey[200]!, + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 设备ID + _buildInfoRow( + icon: Icons.perm_device_info, + label: 'deviceId'.tr, + value: controller.getDeviceId(), + isDarkMode: isDarkMode, + ), + SizedBox(height: 8.h), + + // 固件版本 + _buildInfoRow( + icon: Icons.info_outline, + label: 'firmwareVersion'.tr, + value: controller.firmwareVersion, + isDarkMode: isDarkMode, + ), + + // 产品型号 (如果有) + if (controller.productId.isNotEmpty) ...[ + SizedBox(height: 8.h), + _buildInfoRow( + icon: Icons.category, + label: 'productType'.tr, + value: controller.productId, + isDarkMode: isDarkMode, + ), + ], + + // 连接状态标签 + SizedBox(height: 10.h), + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration( + color: controller.getConnectionStatusColor().withOpacity(0.1), + borderRadius: BorderRadius.circular(6.r), + border: Border.all( + color: controller.getConnectionStatusColor().withOpacity(0.3), + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.check_circle_outline, + size: 12.sp, + color: controller.getConnectionStatusColor(), + ), + SizedBox(width: 4.w), + Text( + controller.getConnectionStatusText(), + style: TextStyle( + fontSize: 11.sp, + color: controller.getConnectionStatusColor(), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + ); + } + + // 信息行 + Widget _buildInfoRow({ + required IconData icon, + required String label, + required String value, + required bool isDarkMode, + }) { + return Row( + children: [ + Icon( + icon, + size: 14.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], + ), + SizedBox(width: 6.w), + Text( + '$label: ', + style: TextStyle( + fontSize: 11.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], + ), + ), + Expanded( + child: Text( + value.isEmpty ? 'unknown'.tr : value, + style: TextStyle( + fontSize: 11.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.grey[300] : Colors.grey[700], + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ); + } + + // 未连接信息 + Widget _buildDisconnectedInfo(bool isDarkMode) { + return Container( + padding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 12.w), + alignment: Alignment.center, + decoration: BoxDecoration( + color: isDarkMode ? Colors.grey[900] : Colors.grey[100], + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: isDarkMode ? Colors.grey[800]! : Colors.grey[300]!, + width: 1, + ), + ), + child: Column( + children: [ + Icon( + Icons.bluetooth_disabled, + size: 32.sp, + color: isDarkMode ? Colors.grey[600] : Colors.grey[500], + ), + SizedBox(height: 12.h), + Text( + 'disconnectedDevice'.tr, // 未连接设备,点击下方按钮连接 + style: TextStyle( + fontSize: 13.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[700], + height: 1.4, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + } + + // 连接按钮 + Widget _buildConnectButton() { + return ElevatedButton( + onPressed: controller.rescanDevices, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + minimumSize: Size(double.infinity, 44.h), + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.r), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.bluetooth_searching, size: 18.sp), + SizedBox(width: 8.w), + Text( + 'connectDevice'.tr, // 连接设备 + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + } + // 热门AI体验标题 Widget _buildSectionTitle(bool isDarkMode) { return Row( diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 9a79931be..b5066624e 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -5,7 +5,8 @@ import 'package:permission_handler/permission_handler.dart'; import '../../../routes/app_routes.dart'; import '../../../core/utils/logger.dart'; import 'dart:io'; -import 'package:classic_bluetooth/classic_bluetooth.dart'; +import '../../../data/services/bluetooth_service.dart'; +import 'package:classic_bluetooth/classic_bluetooth.dart' show BluetoothDevice; /// 配对控制器,负责检查蓝牙权限、状态,并处理配对流程 class PairingController extends GetxController { @@ -21,6 +22,7 @@ class PairingController extends GetxController { // 蓝牙状态 final _bluetoothState = 'unknown'.obs; String get bluetoothState => _bluetoothState.value; + bool get isBluetoothOn => _bluetoothState.value == 'on'; // 状态描述(用于UI显示) final statusDescription = 'initializing'.tr.obs; @@ -28,12 +30,18 @@ class PairingController extends GetxController { // 是否正在跳转到主页 bool _isNavigatingToHome = false; - // 当前连接设备 - final Rx _currentDevice = Rx(null); - BluetoothDevice? get currentDevice => _currentDevice.value; + // 当前连接设备信息 + BluetoothDevice? get currentDevice => _bluetoothService.currentDevice; + + // 是否已连接设备 + bool get isDeviceConnected => currentDevice != null; - // 蓝牙事件订阅 - StreamSubscription? _bluetoothSubscription; + // 服务状态监听 + Worker? _bluetoothStateWorker; + Worker? _deviceWorker; + + // 蓝牙服务 + final _bluetoothService = Get.find(); @override void onInit() { @@ -43,7 +51,8 @@ class PairingController extends GetxController { @override void onClose() { - _bluetoothSubscription?.cancel(); + _bluetoothStateWorker?.dispose(); + _deviceWorker?.dispose(); super.onClose(); } @@ -59,12 +68,12 @@ class PairingController extends GetxController { return; } - // 监听蓝牙状态变化 - _setupBluetoothListener(); + // 设置服务状态监听 + _setupServiceStateListeners(); // 检查蓝牙状态 statusDescription.value = 'checkingBluetoothStatus'.tr; - final isEnabled = await ClassicBluetooth.isBluetoothEnabled(); + final isEnabled = await _bluetoothService.isBluetoothEnabled(); _bluetoothState.value = isEnabled ? 'on' : 'off'; if (!isEnabled) { @@ -127,49 +136,27 @@ class PairingController extends GetxController { return 0; } - /// 设置蓝牙状态监听 - void _setupBluetoothListener() { - _bluetoothSubscription?.cancel(); + /// 设置服务状态监听 + void _setupServiceStateListeners() { + // 监听蓝牙服务的蓝牙状态变化 + _bluetoothStateWorker = ever(_bluetoothService.bluetoothState.obs, (String state) { + _bluetoothState.value = state; + _updateStatusDescription(); + + // 如果蓝牙关闭,可能需要更新UI或提示用户 + if (state == 'off') { + Logger.info('蓝牙已关闭'); + } + }); - try { - _bluetoothSubscription = ClassicBluetooth.events.listen( - (event) { - if (event.type == 'stateChanged' && event.state != null) { - _bluetoothState.value = event.state!; - _updateStatusDescription(); - - // 如果蓝牙关闭,清除设备 - if (event.state == 'off') { - _currentDevice.value = null; - } - } else if (event.type == 'deviceConnected' && event.device != null) { - // 直接从事件中获取设备信息 - final newDevice = event.device!; - - // 更新当前设备 - _currentDevice.value = newDevice; - _updateStatusDescription(); - - // 设备连接时自动跳转到主页 - _autoNavigateToHome(); - } else if (event.type == 'deviceDisconnected' && event.device != null) { - // 直接从事件中获取设备信息 - final disconnectedDevice = event.device!; - - // 如果断开的是当前设备,清除当前设备 - if (_currentDevice.value?.address == disconnectedDevice.address) { - _currentDevice.value = null; - } - _updateStatusDescription(); - } - }, - onError: (error) { - Logger.error('蓝牙事件流错误: $error'); - }, - ); - } catch (e) { - Logger.error('设置蓝牙事件监听器失败: $e'); - } + // 监听当前设备变化 - 使用公开的currentDeviceRx字段 + _deviceWorker = ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) { + if (device != null && !_isNavigatingToHome) { + Logger.info('配对页面发现设备已连接: ${device.name}'); + _autoNavigateToHome(); + } + _updateStatusDescription(); + }); } /// 更新状态描述 @@ -184,7 +171,8 @@ class PairingController extends GetxController { return; } - if (currentDevice != null) { + if (isDeviceConnected) { + // 有设备连接时,显示设备名称 statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}'; return; } @@ -208,15 +196,15 @@ class PairingController extends GetxController { isLoading.value = true; statusDescription.value = 'searchingDevice'.tr; - // 获取已连接的蓝牙设备 - final devices = await ClassicBluetooth.getConnectedDevices(); + // 使用蓝牙服务刷新连接 + await _bluetoothService.refreshDeviceConnection(); - // 如果有设备则设置为当前设备 - if (devices.isNotEmpty) { - _currentDevice.value = devices.first; + // 检查连接状态 + if (isDeviceConnected) { + Logger.info('刷新发现设备已连接: ${currentDevice!.name}'); _autoNavigateToHome(); } else { - _currentDevice.value = null; + Logger.info('刷新未发现已连接设备'); } } catch (e) { Logger.error('刷新设备失败: $e'); @@ -229,22 +217,75 @@ class PairingController extends GetxController { /// 自动跳转到主页 void _autoNavigateToHome() { - if (_isNavigatingToHome || currentDevice == null) return; + if (_isNavigatingToHome) return; + + // 如果没有连接,不进行跳转 + if (!isDeviceConnected) return; _isNavigatingToHome = true; - Logger.info('已找到连接的设备 ${currentDevice?.name},自动跳转到主页'); + + Logger.info('已找到连接的设备 ${currentDevice!.name},自动跳转到主页'); + + // 设备连接时启动BLE后台服务 + _startBleService(); Future.delayed(const Duration(milliseconds: 500), () { Get.offAllNamed(Routes.home); }); } + + /// 启动BLE后台服务 + Future _startBleService() async { + try { + if (!isDeviceConnected) return; + + String deviceName = currentDevice!.name; + String deviceAddress = currentDevice!.address; + + // 准备配置参数 + final Map config = { + 'scan_interval': 5000, // 扫描间隔时间(毫秒) + 'scan_duration': 10000, // 扫描持续时间(毫秒) + 'notification_title': '设备连接中', // 通知标题 + 'notification_content': '已连接到 $deviceName', // 通知内容 + 'mac': deviceAddress, // 设备地址 + 'device_name': deviceName // 设备名称 + }; + + // 启动BLE服务 + final result = await _bluetoothService.startBleService(config); + + if (result) { + Logger.info('BLE后台服务启动成功'); + } else { + Logger.error('BLE后台服务启动失败'); + } + } catch (e) { + Logger.error('启动BLE后台服务发生错误: $e'); + } + } + + /// 停止BLE后台服务 + Future _stopBleService() async { + try { + final result = await _bluetoothService.stopBleService(); + + if (result) { + Logger.info('BLE后台服务停止成功'); + } else { + Logger.error('BLE后台服务停止失败'); + } + } catch (e) { + Logger.error('停止BLE后台服务发生错误: $e'); + } + } /// 打开系统蓝牙设置 void openBluetoothSettings() { try { if (!hasPermissions) { openAppSettings(); - } else if (Platform.isAndroid && bluetoothState == 'off') { + } else if (Platform.isAndroid && !isBluetoothOn) { AppSettings.openAppSettings(type: AppSettingsType.bluetooth); } else if (Platform.isIOS) { openAppSettings(); diff --git a/lib/modules/pairing/views/pairing_view.dart b/lib/modules/pairing/views/pairing_view.dart index 45facd71b..832c524c2 100644 --- a/lib/modules/pairing/views/pairing_view.dart +++ b/lib/modules/pairing/views/pairing_view.dart @@ -199,22 +199,22 @@ class PairingView extends GetView { textAlign: TextAlign.center, ), - // 设备列表(有设备时才显示) - if (controller.connectedDevices.isNotEmpty) ...[ + // 当前连接设备(有设备时才显示) + if (controller.currentDevice != null) ...[ SizedBox(height: 24.h), - _buildConnectedDevicesList(isDarkMode), + _buildConnectedDevice(isDarkMode), ], ], ); }); } - // 已连接设备列表 - Widget _buildConnectedDevicesList(bool isDarkMode) { + // 已连接设备信息 + Widget _buildConnectedDevice(bool isDarkMode) { return Obx(() { - final devices = controller.connectedDevices; + final device = controller.currentDevice; - if (devices.isEmpty) { + if (device == null) { return const SizedBox.shrink(); } @@ -232,7 +232,7 @@ class PairingView extends GetView { ), ), SizedBox(height: 8.h), - ...devices.map((device) => _buildDeviceItem(device, isDarkMode)), + _buildDeviceItem(device, isDarkMode), ], ), ); @@ -261,7 +261,7 @@ class PairingView extends GetView { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - device.name, + device.name ?? 'unknown'.tr, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w500, @@ -270,7 +270,7 @@ class PairingView extends GetView { ), SizedBox(height: 4.h), Text( - device.address, + device.address ?? '', style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.white54 : Colors.black45, diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index d07fe9a22..c40c8c786 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -8,7 +8,6 @@ import '../../../data/services/tts_service.dart'; import '../../../data/services/language_manager.dart'; import '../../../core/utils/logger.dart'; import '../../../data/models/events/voice_interaction_event.dart'; -import '../../../data/services/voice_interaction_service.dart'; import '../../../data/services/asr_service.dart'; import 'package:jieli/jieli.dart'; @@ -39,8 +38,6 @@ class TranslationController extends GetxController { final TtsService _ttsService = Get.find(); final LanguageManager _languageManager = Get.find(); final GetStorage _storage = GetStorage(); - final VoiceInteractionService _voiceInteractionService = - Get.find(); // 存储相关 static const String _historyKey = 'translation_history'; @@ -106,9 +103,7 @@ class TranslationController extends GetxController { targetLanguageCode.value = targetAsrCode; } - // 订阅原生服务事件 - _subscribeToVoiceInteractionService(); - + // 加载历史记录 loadTranslationHistory(); @@ -121,17 +116,6 @@ class TranslationController extends GetxController { }); } - /// 订阅原生服务事件 - void _subscribeToVoiceInteractionService() { - if (_voiceInteractionService.eventStream != null) { - _voiceInteractionSubscription = - _voiceInteractionService.eventStream!.listen((event) { - if (event is RecognitionStartedEvent) { - _handleVoiceInteractionStarted(); - } - }); - } - } // 加载保存的语言设置 void _loadSavedLanguages() { @@ -209,7 +193,6 @@ class TranslationController extends GetxController { Logger.info('开始语音识别'); if (isRecognizing.value) return; - _voiceInteractionService.pauseVoiceInteraction(); try { Logger.info('开始语音识别1'); diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 457d75308..a483ef0ae 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -37,8 +37,6 @@ import '../modules/jieli_test/views/jieli_test_view.dart'; import '../modules/jieli_test/bindings/jieli_test_binding.dart'; import '../modules/opus_test/views/opus_test_view.dart'; import '../modules/opus_test/bindings/opus_test_binding.dart'; -import '../modules/ble_test/bindings/ble_test_binding.dart'; -import '../modules/ble_test/views/ble_test_view.dart'; import '../modules/speech_test/views/speech_test_view.dart'; import '../modules/speech_test/bindings/speech_test_binding.dart'; @@ -144,11 +142,7 @@ abstract class AppPages { page: () => const OpusTestView(), binding: OpusTestBinding(), ), - GetPage( - name: Routes.bleTest, - page: () => const BleTestView(), - binding: BleTestBinding(), - ), + GetPage( name: Routes.speechTest, page: () => const SpeechTestView(), diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleForegroundService.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleForegroundService.kt index 88a496745..c7e0bd510 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleForegroundService.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleForegroundService.kt @@ -48,23 +48,28 @@ class BleForegroundService : Service(), BleService.Callback { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { FileLogger.i(TAG, "服务onStartCommand: ${intent?.action}") - // 1. 启动前台服务 + // 1. 立即启动前台服务,这必须是第一步操作 startForegroundServiceWithNotification() - // 2. 注册后台扫描 - registerBackgroundScan() - - // 3. 处理连接请求 - val macAddress = intent?.getStringExtra("mac") - if (!macAddress.isNullOrEmpty()) { - // 检查当前连接状态,如果未连接则发起连接 - serviceScope.launch { - if (!bleManager.isConnected()) { - FileLogger.i(TAG, "开始连接到设备: $macAddress") - connectToDevice(macAddress) - } else { - FileLogger.i(TAG, "设备已连接,忽略连接请求") + // 添加延迟确保通知已显示 + serviceScope.launch { + try { + // 2. 注册后台扫描 + registerBackgroundScan() + + // 3. 处理连接请求 + val macAddress = intent?.getStringExtra("mac") + if (!macAddress.isNullOrEmpty()) { + // 检查当前连接状态,如果未连接则发起连接 + if (!bleManager.isConnected()) { + FileLogger.i(TAG, "开始连接到设备: $macAddress") + connectToDevice(macAddress) + } else { + FileLogger.i(TAG, "设备已连接,忽略连接请求") + } } + } catch (e: Exception) { + FileLogger.e(TAG, "服务启动后处理异常: ${e.message}", e) } } @@ -221,7 +226,7 @@ class BleForegroundService : Service(), BleService.Callback { // 以下是必须实现但当前服务不需要处理的回调方法 override fun onScanResult(devices: List>) {} override fun onAudioDataReceived(data: ByteArray) {} - override fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean) {} - override fun onDeviceInfoReceived(infoType: Int, infoData: String) {} - override fun onStatusUpdate(status: Map) {} + override fun onDeviceInfoReceived(infoType: Int, infoData: Map) { + // 前台服务不需要处理设备信息 + } } \ No newline at end of file diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleManagerWrapper.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleManagerWrapper.kt index e0f5b04b2..a6435c531 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleManagerWrapper.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleManagerWrapper.kt @@ -71,19 +71,8 @@ class BleManagerWrapper private constructor() { } } } - - - override fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean) { - for (callback in registeredCallbacks) { - try { - callback.onProtocolCommandResult(commandType, data, success) - } catch (e: Exception) { - FileLogger.e(TAG, "分发协议命令结果回调异常", e) - } - } - } - override fun onDeviceInfoReceived(infoType: Int, infoData: String) { + override fun onDeviceInfoReceived(infoType: Int, infoData: Map) { for (callback in registeredCallbacks) { try { callback.onDeviceInfoReceived(infoType, infoData) @@ -92,16 +81,6 @@ class BleManagerWrapper private constructor() { } } } - - override fun onStatusUpdate(status: Map) { - for (callback in registeredCallbacks) { - try { - callback.onStatusUpdate(status) - } catch (e: Exception) { - FileLogger.e(TAG, "分发状态更新回调异常", e) - } - } - } } /** 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 92cabf152..92aad6bda 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 @@ -52,14 +52,8 @@ class BleService(private val context: Context) { fun onAudioDataReceived(data: ByteArray) fun onWakeupSignalReceived() - // 命令响应相关回调 - fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean) - - // 设备信息相关回调 - fun onDeviceInfoReceived(infoType: Int, infoData: String) - - // 状态更新相关回调 - fun onStatusUpdate(status: Map) + // 设备信息相关回调 - 统一回调接口 + fun onDeviceInfoReceived(infoType: Int, infoData: Map) } // ====================================================================================================== @@ -347,9 +341,11 @@ class BleService(private val context: Context) { } else { FileLogger.w(TAG, "音频服务特征未找到") } + } override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) { + FileLogger.d(TAG, "收到特征数据: ${c.uuid}, 数据=${c.value?.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") val data = c.value ?: return // 根据特征UUID区分处理 @@ -439,6 +435,7 @@ class BleService(private val context: Context) { * 处理设备响应数据 - 帧头为0xBB */ private fun processDeviceResponse(data: ByteArray) { + FileLogger.d(TAG, "收到设备响应: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") if (data.size < 3) { FileLogger.e(TAG, "设备响应数据格式错误:数据长度过短") return @@ -450,7 +447,13 @@ class BleService(private val context: Context) { // 判断命令失败响应(长度为0) if (data.size == 4 && data[2].toInt() and 0xFF == 0) { FileLogger.w(TAG, "命令 0x${cmdType.toString(16)} 执行失败") - callback?.onProtocolCommandResult(cmdType, null, false) + // 创建失败结果Map + val resultMap = mapOf( + "success" to false, + "command" to cmdType, + "rawData" to byteArrayOf() + ) + callback?.onDeviceInfoReceived(cmdType, resultMap) return } @@ -463,14 +466,13 @@ class BleService(private val context: Context) { return } - // 计算CRC校验 - val crcData = ByteArray(2 + length) // 命令 + 长度 + 数据 - System.arraycopy(data, 1, crcData, 0, crcData.size) - val calculatedCrc = crc8Maxim(crcData, crcData.size) - val receivedCrc = data[data.size - 1] + // 计算CRC校验 - 修正:计算除最后一个CRC字节外的所有字节 + val payload = data.sliceArray(0 until data.size - 1) + val calculatedCrc = crc8Maxim(payload, payload.size) + val receivedCrc = data[data.size - 1].toInt() and 0xFF - if (calculatedCrc != receivedCrc) { - FileLogger.e(TAG, "设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc.toInt() and 0xFF}") + if (calculatedCrc.toInt() and 0xFF != receivedCrc) { + FileLogger.e(TAG, "设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}") return } @@ -485,45 +487,97 @@ class BleService(private val context: Context) { // 根据命令类型处理不同的响应 when (cmdType) { BleConst.CMD_GET_VERSION -> { + val resultMap = mutableMapOf( + "success" to true, + "command" to cmdType, + "rawData" to responseData + ) + if (responseData.isNotEmpty()) { // 转换为字符串前,确认是否是有效的ASCII字符 val version = responseData.joinToString(".") { (it.toInt() and 0xFF).toString() } firmwareVersion = version - callback?.onDeviceInfoReceived(BleConst.CMD_GET_VERSION, version) + resultMap["version"] = version FileLogger.i(TAG, "接收到目标版本号: $version") } - callback?.onProtocolCommandResult(cmdType, responseData, true) + + callback?.onDeviceInfoReceived(cmdType, resultMap) } BleConst.CMD_GET_PRODUCT_ID -> { + val resultMap = mutableMapOf( + "success" to true, + "command" to cmdType, + "rawData" to responseData + ) + if (responseData.isNotEmpty() && responseData.size >= 2) { val productId = "0x${(responseData[0].toInt() and 0xFF).toString(16)}${(responseData[1].toInt() and 0xFF).toString(16)}" + resultMap["productId"] = productId FileLogger.i(TAG, "接收到产品ID: $productId") - callback?.onDeviceInfoReceived(BleConst.CMD_GET_PRODUCT_ID, productId) } - callback?.onProtocolCommandResult(cmdType, responseData, true) + + callback?.onDeviceInfoReceived(cmdType, resultMap) } BleConst.CMD_GET_COLOR_ID -> { + val resultMap = mutableMapOf( + "success" to true, + "command" to cmdType, + "rawData" to responseData + ) + if (responseData.isNotEmpty()) { val colorId = (responseData[0].toInt() and 0xFF).toString() + resultMap["colorId"] = colorId FileLogger.i(TAG, "接收到耳机颜色ID: $colorId") - callback?.onDeviceInfoReceived(BleConst.CMD_GET_COLOR_ID, colorId) } - callback?.onProtocolCommandResult(cmdType, responseData, true) + + callback?.onDeviceInfoReceived(cmdType, resultMap) } BleConst.CMD_GET_BATTERY_INFO -> { + val resultMap = mutableMapOf( + "success" to true, + "command" to cmdType, + "rawData" to responseData + ) + if (responseData.isNotEmpty() && responseData.size >= 3) { - val batteryLevel = responseData[0].toInt() and 0x7F - val isCharging = (responseData[0].toInt() and 0x80) != 0 - FileLogger.i(TAG, "接收到电量信息: ${batteryLevel}%, ${if(isCharging) "充电中" else "未充电"}") + // 处理电池信息 - 左耳、右耳、充电仓 + val leftEarBattery = responseData[0].toInt() and 0x7F + val leftEarCharging = (responseData[0].toInt() and 0x80) != 0 + + val rightEarBattery = if (responseData.size > 1) responseData[1].toInt() and 0x7F else 0 + val rightEarCharging = if (responseData.size > 1) (responseData[1].toInt() and 0x80) != 0 else false - // 更新电池电量 - this.batteryLevel = batteryLevel + val caseBattery = if (responseData.size > 2) responseData[2].toInt() and 0x7F else 0 + val caseCharging = if (responseData.size > 2) (responseData[2].toInt() and 0x80) != 0 else false - callback?.onDeviceInfoReceived(BleConst.CMD_GET_BATTERY_INFO, "$batteryLevel|${if(isCharging) "1" else "0"}") + // 更新整体电池电量 (使用左耳电量作为主要指示) + this.batteryLevel = leftEarBattery + + // 添加到结果Map + resultMap["leftEarBattery"] = leftEarBattery + resultMap["leftEarCharging"] = leftEarCharging + resultMap["rightEarBattery"] = rightEarBattery + resultMap["rightEarCharging"] = rightEarCharging + resultMap["caseBattery"] = caseBattery + resultMap["caseCharging"] = caseCharging + resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示 + + FileLogger.i(TAG, "接收到电量信息: 左耳=${leftEarBattery}% " + + "(${if(leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " + + "(${if(rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " + + "(${if(caseCharging) "充电中" else "未充电"})") } - callback?.onProtocolCommandResult(cmdType, responseData, true) + + callback?.onDeviceInfoReceived(cmdType, resultMap) } BleConst.CMD_CONTROL_CODEC -> { + val resultMap = mutableMapOf( + "success" to true, + "command" to cmdType, + "rawData" to responseData + ) + if (responseData.isNotEmpty() && responseData.size >= 2) { val codecStatus = responseData[0].toInt() and 0xFF val channelMode = responseData[1].toInt() and 0xFF @@ -545,13 +599,26 @@ class BleService(private val context: Context) { else -> "未知声道($channelMode)" } + resultMap["codecStatus"] = codecStatus + resultMap["channelMode"] = channelMode + resultMap["statusDesc"] = statusDesc + resultMap["channelDesc"] = channelDesc + FileLogger.i(TAG, "编解码控制响应: $statusDesc, $channelDesc") } - callback?.onProtocolCommandResult(cmdType, responseData, true) + + callback?.onDeviceInfoReceived(cmdType, resultMap) } else -> { + // 对于未知命令,返回原始数据 + val resultMap = mapOf( + "success" to true, + "command" to cmdType, + "rawData" to responseData, + "isUnknown" to true + ) FileLogger.d(TAG, "收到未知命令响应: 0x${cmdType.toString(16)}") - callback?.onProtocolCommandResult(cmdType, responseData, true) + callback?.onDeviceInfoReceived(cmdType, resultMap) } } } @@ -560,6 +627,7 @@ class BleService(private val context: Context) { * 处理设备主动上报数据 - 帧头为0xCC */ private fun processDeviceNotification(data: ByteArray) { + FileLogger.d(TAG, "收到设备主动上报: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") if (data.size < 3) { FileLogger.e(TAG, "设备主动上报数据格式错误:数据长度过短") return @@ -575,14 +643,13 @@ class BleService(private val context: Context) { return } - // 计算CRC校验 - val crcData = ByteArray(2 + length) // 类型 + 长度 + 数据 - System.arraycopy(data, 1, crcData, 0, crcData.size) - val calculatedCrc = crc8Maxim(crcData, crcData.size) - val receivedCrc = data[data.size - 1] + // 计算CRC校验 - 修正:计算除最后一个CRC字节外的所有字节 + val payload = data.sliceArray(0 until data.size - 1) + val calculatedCrc = crc8Maxim(payload, payload.size) + val receivedCrc = data[data.size - 1].toInt() and 0xFF - if (calculatedCrc != receivedCrc) { - FileLogger.e(TAG, "设备主动上报CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc.toInt() and 0xFF}") + if (calculatedCrc.toInt() and 0xFF != receivedCrc) { + FileLogger.e(TAG, "设备主动上报CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}") return } @@ -597,20 +664,56 @@ class BleService(private val context: Context) { // 根据上报类型处理 when (infoType) { BleConst.CMD_GET_BATTERY_INFO -> { // 电量信息 + val resultMap = mutableMapOf( + "success" to true, + "command" to infoType, + "rawData" to notifyData, + "isNotification" to true // 标记为主动上报 + ) + if (notifyData.isNotEmpty() && notifyData.size >= 3) { - val batteryLevel = notifyData[0].toInt() and 0x7F - val isCharging = (notifyData[0].toInt() and 0x80) != 0 - FileLogger.i(TAG, "接收到电量信息: ${batteryLevel}%, ${if(isCharging) "充电中" else "未充电"}") + // 处理电池信息 - 左耳、右耳、充电仓 + val leftEarBattery = notifyData[0].toInt() and 0x7F + val leftEarCharging = (notifyData[0].toInt() and 0x80) != 0 + + val rightEarBattery = if (notifyData.size > 1) notifyData[1].toInt() and 0x7F else 0 + val rightEarCharging = if (notifyData.size > 1) (notifyData[1].toInt() and 0x80) != 0 else false + + val caseBattery = if (notifyData.size > 2) notifyData[2].toInt() and 0x7F else 0 + val caseCharging = if (notifyData.size > 2) (notifyData[2].toInt() and 0x80) != 0 else false - // 更新电池电量 - this.batteryLevel = batteryLevel + // 更新整体电池电量 (使用左耳电量作为主要指示) + this.batteryLevel = leftEarBattery - callback?.onDeviceInfoReceived(BleConst.CMD_GET_BATTERY_INFO, "$batteryLevel|${if(isCharging) "1" else "0"}") + // 添加到结果Map + resultMap["leftEarBattery"] = leftEarBattery + resultMap["leftEarCharging"] = leftEarCharging + resultMap["rightEarBattery"] = rightEarBattery + resultMap["rightEarCharging"] = rightEarCharging + resultMap["caseBattery"] = caseBattery + resultMap["caseCharging"] = caseCharging + resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示 + + FileLogger.i(TAG, "收到主动上报电量信息: 左耳=${leftEarBattery}% " + + "(${if(leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " + + "(${if(rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " + + "(${if(caseCharging) "充电中" else "未充电"})") } + + callback?.onDeviceInfoReceived(infoType, resultMap) } // 可以在这里添加其他主动上报类型的处理 else -> { + // 对于未知上报类型,返回原始数据 + val resultMap = mapOf( + "success" to true, + "command" to infoType, + "rawData" to notifyData, + "isUnknown" to true, + "isNotification" to true // 标记为主动上报 + ) FileLogger.d(TAG, "收到未知主动上报类型: 0x${infoType.toString(16)}") + callback?.onDeviceInfoReceived(infoType, resultMap) } } } @@ -863,10 +966,9 @@ class BleService(private val context: Context) { System.arraycopy(cmdData, 0, cmdPacket, 3, cmdLength) } - // 计算CRC - 对命令类型、长度和数据进行校验 - val crcData = ByteArray(2 + cmdLength) - System.arraycopy(cmdPacket, 1, crcData, 0, crcData.size) - val crc = crc8Maxim(crcData, crcData.size) + // 计算CRC - 对帧头到数据的所有字节进行校验 (不包括CRC自身) + val crcPayload = cmdPacket.sliceArray(0 until cmdPacket.size - 1) + val crc = crc8Maxim(crcPayload, crcPayload.size) // 设置CRC cmdPacket[3 + cmdLength] = crc diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt index a6eb22e57..3a0ef4841 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt @@ -1,10 +1,10 @@ package com.yunqiinnovation.ble_service import android.content.Context +import android.content.Intent import android.os.Build import android.os.Handler import android.os.Looper -import android.util.Log import androidx.annotation.NonNull import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware @@ -21,7 +21,7 @@ import com.yunqiinnovation.deepvoice_utils.FileLogger * BleServicePlugin * 作为Flutter与原生BLE通信的插件接口 */ -class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.RequestPermissionsResultListener, BleService.Callback { +class BleServicePlugin : FlutterPlugin, MethodCallHandler, BleService.Callback { private val TAG = "BleServicePlugin" // 添加主线程Handler @@ -30,9 +30,7 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin // 方法通道 private lateinit var methodChannel: MethodChannel - // 事件通道 - private var scanEventChannel: EventChannel? = null - private var connectionEventChannel: EventChannel? = null + // 事件通道 - 只保留数据和状态通道 private var dataEventChannel: EventChannel? = null private var statusEventChannel: EventChannel? = null @@ -42,9 +40,7 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin // BleManager包装器 private val bleManager = BleManagerWrapper.instance - // 事件接收器 - private var scanEventSink: EventChannel.EventSink? = null - private var connectionEventSink: EventChannel.EventSink? = null + // 事件接收器 - 只保留数据和状态通道 private var dataEventSink: EventChannel.EventSink? = null private var statusEventSink: EventChannel.EventSink? = null @@ -56,7 +52,7 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.ble_service") methodChannel.setMethodCallHandler(this) - // 设置事件通道 + // 设置事件通道 - 只保留必要的通道 setupEventChannels(flutterPluginBinding) context = flutterPluginBinding.applicationContext @@ -65,44 +61,14 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin try { bleManager.init(context) bleManager.addCallback(this) - - // 开始定期状态更新 - startPeriodicStatusUpdates() - FileLogger.d(TAG, "BleManagerWrapper initialized successfully") } catch (e: Exception) { FileLogger.e(TAG, "Failed to initialize BleManagerWrapper: ${e.message}", e) } } - // 设置事件通道 + // 设置事件通道 - 简化版 private fun setupEventChannels(binding: FlutterPlugin.FlutterPluginBinding) { - // 扫描结果事件通道 - scanEventChannel = EventChannel(binding.binaryMessenger, "com.yunqiinnovation.ble_service/scan_events").apply { - setStreamHandler(object : EventChannel.StreamHandler { - override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { - scanEventSink = events - } - - override fun onCancel(arguments: Any?) { - scanEventSink = null - } - }) - } - - // 连接状态事件通道 - connectionEventChannel = EventChannel(binding.binaryMessenger, "com.yunqiinnovation.ble_service/connection_events").apply { - setStreamHandler(object : EventChannel.StreamHandler { - override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { - connectionEventSink = events - } - - override fun onCancel(arguments: Any?) { - connectionEventSink = null - } - }) - } - // 数据接收事件通道 dataEventChannel = EventChannel(binding.binaryMessenger, "com.yunqiinnovation.ble_service/data_events").apply { setStreamHandler(object : EventChannel.StreamHandler { @@ -121,8 +87,6 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { statusEventSink = events - // 立即发送一次状态更新 - sendStatusUpdate() } override fun onCancel(arguments: Any?) { @@ -132,31 +96,6 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin } } - // 开始定期状态更新 - private fun startPeriodicStatusUpdates() { - // 添加节流标志 - var lastUpdateTime = 0L - - Thread { - try { - while (!Thread.interrupted()) { - val currentTime = System.currentTimeMillis() - // 确保至少间隔5秒才发送一次状态更新 - if (currentTime - lastUpdateTime >= 5000) { - sendStatusUpdate() - lastUpdateTime = currentTime - } - Thread.sleep(3000) // 保持同样的休眠时间 - } - } catch (e: InterruptedException) { - FileLogger.d(TAG, "状态更新线程已停止") - } - }.apply { - isDaemon = true - start() - } - } - // 添加sendEvent函数 private fun sendEvent(eventSink: EventChannel.EventSink?, data: T, errorPrefix: String) { if (eventSink == null) return @@ -178,36 +117,6 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin } } } - - // 修改sendStatusUpdate方法,使用新的sendEvent函数 - private fun sendStatusUpdate() { - try { - val connectionState = bleManager.getConnectionState() - - val stateStr = when(connectionState) { - BleConst.STATE_DISCONNECTED -> "已断开" - BleConst.STATE_CONNECTING -> "连接中" - BleConst.STATE_CONNECTED -> "已连接" - BleConst.STATE_DISCONNECTING -> "断开中" - BleConst.STATE_ERROR -> "连接错误" - else -> "未知状态($connectionState)" - } - - val stateMap = mapOf( - "type" to "statusUpdate", - "connectionState" to connectionState, - "connectionStateStr" to stateStr, - "batteryLevel" to bleManager.getBatteryLevel(), - "firmwareVersion" to (bleManager.getFirmwareVersion() ?: "未知"), - "connectedDevice" to (bleManager.getConnectedDeviceAddress() ?: "") - ) - - // 使用sendEvent函数发送事件 - sendEvent(statusEventSink, stateMap, "发送状态更新失败") - } catch (e: Exception) { - FileLogger.e(TAG, "准备状态更新数据失败: ${e.message}", e) - } - } override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { methodChannel.setMethodCallHandler(null) @@ -216,8 +125,6 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin bleManager.removeCallback(this) // 解除事件通道 - scanEventChannel?.setStreamHandler(null) - connectionEventChannel?.setStreamHandler(null) dataEventChannel?.setStreamHandler(null) statusEventChannel?.setStreamHandler(null) } @@ -226,59 +133,10 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin FileLogger.d(TAG, "Method call: ${call.method}") when (call.method) { - "initialize" -> { - try { - bleManager.init(context) - result.success(true) - } catch (e: Exception) { - result.error("INIT_ERROR", "初始化BLE服务失败: ${e.message}", null) - } - } - "registerCallback" -> { - try { - bleManager.addCallback(this) - result.success(true) - } catch (e: Exception) { - result.error("CALLBACK_ERROR", "设置回调失败: ${e.message}", null) - } - } - "getPlatformVersion" -> { - result.success("Android ${Build.VERSION.RELEASE}") - } - "startScan" -> { - val timeout = call.argument("timeout")?.toLong() ?: 10000L - val success = bleManager.startScan(timeout) - result.success(success) - } - "stopScan" -> { - val success = bleManager.stopScan() - result.success(success) - } - "connect" -> { - val address = call.argument("address") - if (address == null) { - result.error("INVALID_ARGUMENT", "Address cannot be null", null) - return - } - bleManager.connect(address) - result.success(true) - } - "disconnect" -> { - bleManager.disconnect() - result.success(true) - } "isConnected" -> { val connected = bleManager.isConnected() result.success(connected) } - "getConnectionState" -> { - val state = bleManager.getConnectionState() - result.success(state) - } - "getConnectedDeviceAddress" -> { - val address = bleManager.getConnectedDeviceAddress() - result.success(address) - } "getBatteryLevel" -> { val level = bleManager.getBatteryLevel() result.success(level) @@ -311,21 +169,28 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin val success = bleManager.playA2DP() result.success(success) } - "volumeUp" -> { - val success = bleManager.volumeUp() - result.success(success) - } - "volumeDown" -> { - val success = bleManager.volumeDown() - result.success(success) - } - "cleanup" -> { - bleManager.cleanup() - result.success(true) + + "startBleService" -> { + try { + // 从Flutter获取配置参数 + val configParams = call.arguments as? Map ?: mapOf() + + // 启动BLE服务并传递配置参数 + startBleServiceWithConfig(configParams) + result.success(true) + } catch (e: Exception) { + FileLogger.e(TAG, "启动BLE服务失败", e) + result.error("BLE_SERVICE_ERROR", "启动BLE服务失败: ${e.message}", null) + } } - "getScannedDevices" -> { - val devices = bleManager.getScannedDevices() - result.success(devices) + "stopBleService" -> { + try { + stopBleService() + result.success(true) + } catch (e: Exception) { + FileLogger.e(TAG, "停止BLE服务失败", e) + result.error("BLE_SERVICE_ERROR", "停止BLE服务失败: ${e.message}", null) + } } else -> { result.notImplemented() @@ -333,74 +198,96 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin } } - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - binding.addRequestPermissionsResultListener(this) - } - - override fun onDetachedFromActivityForConfigChanges() { - // 无需实现 - } - - override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { - binding.addRequestPermissionsResultListener(this) - } - - override fun onDetachedFromActivity() { - // 无需实现 - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ): Boolean { - // 简化权限处理 - return false - } - // BleService.Callback 接口实现 override fun onScanResult(devices: List>) { - sendEvent(scanEventSink, mapOf("devices" to devices), "发送扫描结果异常") + } override fun onConnectionStateChanged(state: Int) { - sendEvent(connectionEventSink, state, "发送连接状态异常") - sendStatusUpdate() + // 连接状态变化通过状态通道发送 + val stateStr = when(state) { + BleConst.STATE_DISCONNECTED -> "Disconnected" + BleConst.STATE_CONNECTING -> "Connecting" + BleConst.STATE_CONNECTED -> "Connected" + BleConst.STATE_DISCONNECTING -> "Disconnecting" + BleConst.STATE_ERROR -> "Connection Error" + else -> "Unknown State($state)" + } + + val stateMap = mapOf( + "type" to "connectionStateChanged", + "state" to state, + + ) + + sendEvent(statusEventSink, stateMap, "发送连接状态异常") } - override fun onAudioDataReceived(data: ByteArray) { - sendEvent(dataEventSink, mapOf("type" to "audioData", "data" to data), "发送音频数据异常") + // sendEvent(dataEventSink, mapOf("type" to "audioData", "data" to data), "发送音频数据异常") } override fun onWakeupSignalReceived() { - sendEvent(dataEventSink, mapOf("type" to "wakeup"), "发送唤醒信号异常") + sendEvent(statusEventSink, mapOf("type" to "wakeup"), "发送唤醒信号异常") } - - override fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean) { - sendEvent(connectionEventSink, mapOf( - "type" to "protocolCommandResult", - "commandType" to commandType, - "data" to (data?.let { String(it) } ?: ""), - "success" to success - ), "发送协议命令结果异常") + override fun onDeviceInfoReceived(infoType: Int, infoData: Map) { + // 设备信息通过状态通道发送 + val resultMap = infoData.toMutableMap() + resultMap["type"] = "deviceInfo" + resultMap["result"] = infoType + + + // 发送设备信息事件 + sendEvent(statusEventSink, resultMap, "发送设备信息异常") } - override fun onDeviceInfoReceived(infoType: Int, infoData: String) { - sendEvent(statusEventSink, mapOf( - "type" to "deviceInfo", - "infoType" to infoType, - "infoData" to infoData - ), "发送设备信息异常") - sendStatusUpdate() + /** + * 使用配置参数启动BLE服务 + */ + private fun startBleServiceWithConfig(configParams: Map) { + try { + // 初始化BleManagerWrapper + val bleManager = BleManagerWrapper.instance + bleManager.init(context) + + // 启动BLE前台服务 + val serviceIntent = Intent(context, BleForegroundService::class.java) + + // 如果有额外配置需要传递给服务,可以通过Intent传递 + configParams.forEach { (key, value) -> + when (value) { + is String -> serviceIntent.putExtra(key, value) + is Boolean -> serviceIntent.putExtra(key, value) + is Int -> serviceIntent.putExtra(key, value) + is Double -> serviceIntent.putExtra(key, value.toFloat()) + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(serviceIntent) + } else { + context.startService(serviceIntent) + } + + FileLogger.d(TAG, "成功启动BLE服务,配置参数: $configParams") + } catch (e: Exception) { + FileLogger.e(TAG, "BLE服务启动失败", e) + throw e + } } - override fun onStatusUpdate(status: Map) { - sendEvent(statusEventSink, mapOf( - "type" to "statusUpdate", - "status" to status - ), "发送状态更新异常") + /** + * 停止BLE服务 + */ + private fun stopBleService() { + try { + context.stopService(Intent(context, BleForegroundService::class.java)) + FileLogger.d(TAG, "成功停止BLE服务") + } catch (e: Exception) { + FileLogger.e(TAG, "停止BLE服务失败", e) + throw e + } } } \ No newline at end of file diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleWakeReceiver.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleWakeReceiver.kt index 8b1e0a822..cf3def93d 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleWakeReceiver.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleWakeReceiver.kt @@ -5,6 +5,8 @@ import android.bluetooth.le.* import android.os.Build import com.yunqiinnovation.deepvoice_utils.FileLogger import android.util.Log +import android.os.Handler +import android.os.Looper /** * BLE唤醒接收器 * 当耳机发送含特定UUID广播时,系统会触发此接收器 @@ -17,21 +19,72 @@ class BleWakeReceiver : BroadcastReceiver() { } override fun onReceive(ctx: Context, intent: Intent) { - FileLogger.init(ctx) - FileLogger.d(TAG, "onReceive — 收到唤醒广播: action=${intent.action}, ${BleConst.ACTION_SCAN_RESULT}") - - if (intent.action != BleConst.ACTION_SCAN_RESULT) return - - val list = intent.getParcelableArrayListExtra( - BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT - )?: return - val mac = list.first().device.address - FileLogger.d(TAG, "被系统唤醒,发现设备 $mac") - - // 拉起前台服务并把MAC传过去 - val svc = Intent(ctx, BleForegroundService::class.java).putExtra("mac", mac) - svc.action = BleConst.ACTION_SCAN_RESULT - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - ctx.startForegroundService(svc) else ctx.startService(svc) + // 获取PendingResult以防止接收器过早结束 + val pendingResult = goAsync() + + try { + FileLogger.init(ctx) + FileLogger.d(TAG, "onReceive — 收到唤醒广播: action=${intent.action}, ${BleConst.ACTION_SCAN_RESULT}") + + if (intent.action != BleConst.ACTION_SCAN_RESULT) { + pendingResult.finish() + return + } + + val list = intent.getParcelableArrayListExtra( + BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT + )?: run { + pendingResult.finish() + return + } + + if (list.isEmpty()) { + FileLogger.d(TAG, "扫描结果为空") + pendingResult.finish() + return + } + + val mac = list.first().device.address + FileLogger.d(TAG, "被系统唤醒,发现设备 $mac") + + // 使用Handler确保在主线程上下文中启动服务 + Handler(Looper.getMainLooper()).post { + try { + // 首先尝试启动普通服务 + val svc = Intent(ctx, BleForegroundService::class.java).apply { + putExtra("mac", mac) + action = BleConst.ACTION_SCAN_RESULT + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // 对于Android 8及以上,优先尝试普通服务启动,如果APP在前台可以成功 + try { + ctx.startService(svc) + FileLogger.d(TAG, "使用普通服务启动成功") + } catch (e: Exception) { + FileLogger.d(TAG, "普通服务启动失败,尝试前台服务: ${e.message}") + // 尝试前台服务启动 + try { + ctx.startForegroundService(svc) + FileLogger.d(TAG, "前台服务启动成功") + } catch (e: Exception) { + FileLogger.e(TAG, "前台服务启动也失败: ${e.message}", e) + } + } + } else { + ctx.startService(svc) + } + } catch (e: Exception) { + FileLogger.e(TAG, "启动服务异常: ${e.message}", e) + } finally { + // 确保完成pendingResult + pendingResult.finish() + } + } + } catch (e: Exception) { + FileLogger.e(TAG, "处理BLE广播异常: ${e.message}", e) + pendingResult.finish() + } } } \ No newline at end of file diff --git a/local_plugins/ble_service/lib/ble_service.dart b/local_plugins/ble_service/lib/ble_service.dart index 92d866a67..c42707b75 100644 --- a/local_plugins/ble_service/lib/ble_service.dart +++ b/local_plugins/ble_service/lib/ble_service.dart @@ -12,35 +12,6 @@ class BleConnectionState { static const int error = 4; } -/// BLE设备数据模型 -class BleDevice { - final String id; - final String name; - final int rssi; - final List uuids; - - BleDevice({ - required this.id, - required this.name, - required this.rssi, - required this.uuids, - }); - - factory BleDevice.fromMap(Map map) { - final uuidsList = (map['uuids'] as List?)?.cast() ?? []; - - return BleDevice( - id: map['id'] as String, - name: map['name'] as String? ?? 'Unknown', - rssi: map['rssi'] as int, - uuids: uuidsList, - ); - } - - @override - String toString() => 'BleDevice(id: $id, name: $name, rssi: $rssi, uuids: $uuids)'; -} - /// BLE服务插件 class BleService { static final BleService _instance = BleService._internal(); @@ -51,119 +22,30 @@ class BleService { // 方法通道 final MethodChannel _methodChannel = const MethodChannel('com.yunqiinnovation.ble_service'); - // 事件通道 - late final EventChannel _scanEventChannel; - late final EventChannel _connectionEventChannel; + // 事件通道 - 简化为只使用数据和状态通道 late final EventChannel _dataEventChannel; late final EventChannel _statusEventChannel; // 事件流 - late final Stream> _scanStream; - late final Stream _connectionStream; late final Stream _dataStream; late final Stream _statusStream; // 获取事件流 - Stream> get onScanResults => _scanStream; - Stream get onConnectionChanged => _connectionStream; Stream get onDataReceived => _dataStream; + + // 状态流直接传递原始数据 Stream get onStatusUpdated => _statusStream; // 内部构造函数 BleService._internal() { - _scanEventChannel = const EventChannel('com.yunqiinnovation.ble_service/scan_events'); - _connectionEventChannel = const EventChannel('com.yunqiinnovation.ble_service/connection_events'); _dataEventChannel = const EventChannel('com.yunqiinnovation.ble_service/data_events'); _statusEventChannel = const EventChannel('com.yunqiinnovation.ble_service/status_events'); // 初始化事件流 - _scanStream = _scanEventChannel.receiveBroadcastStream().map>((event) { - final List devices = (event['devices'] as List?) ?? []; - return devices.map((device) => BleDevice.fromMap(device)).toList(); - }); - - _connectionStream = _connectionEventChannel.receiveBroadcastStream(); _dataStream = _dataEventChannel.receiveBroadcastStream(); _statusStream = _statusEventChannel.receiveBroadcastStream(); } - /// 初始化BLE服务 - Future initialize() async { - try { - final result = await _methodChannel.invokeMethod('initialize'); - return result ?? false; - } catch (e) { - print('初始化BLE服务失败: $e'); - return false; - } - } - - /// 注册BLE回调 - Future registerCallback() async { - try { - final result = await _methodChannel.invokeMethod('registerCallback'); - return result ?? false; - } catch (e) { - print('注册BLE回调失败: $e'); - return false; - } - } - - /// 设置BLE方法回调处理器 - void setMethodCallHandler(Future Function(MethodCall call) handler) { - _methodChannel.setMethodCallHandler(handler); - } - - /// 开始扫描设备 - Future startScan({int timeout = 10000}) async { - try { - final result = await _methodChannel.invokeMethod( - 'startScan', - {'timeout': timeout}, - ); - return result ?? false; - } catch (e) { - print('开始扫描设备失败: $e'); - return false; - } - } - - /// 停止扫描设备 - Future stopScan() async { - try { - final result = await _methodChannel.invokeMethod('stopScan'); - return result ?? false; - } catch (e) { - print('停止扫描设备失败: $e'); - return false; - } - } - - /// 连接到设备 - Future connect(String address) async { - try { - final result = await _methodChannel.invokeMethod( - 'connect', - {'address': address}, - ); - return result ?? false; - } catch (e) { - print('连接设备失败: $e'); - return false; - } - } - - /// 断开设备连接 - Future disconnect() async { - try { - final result = await _methodChannel.invokeMethod('disconnect'); - return result ?? false; - } catch (e) { - print('断开设备连接失败: $e'); - return false; - } - } - /// 检查是否已连接 Future isConnected() async { try { @@ -175,27 +57,6 @@ class BleService { } } - /// 获取连接状态 - Future getConnectionState() async { - try { - final result = await _methodChannel.invokeMethod('getConnectionState'); - return result ?? BleConnectionState.disconnected; - } catch (e) { - print('获取连接状态失败: $e'); - return BleConnectionState.disconnected; - } - } - - /// 获取已连接设备地址 - Future getConnectedDeviceAddress() async { - try { - return await _methodChannel.invokeMethod('getConnectedDeviceAddress'); - } catch (e) { - print('获取已连接设备地址失败: $e'); - return null; - } - } - /// 获取电池电量 Future getBatteryLevel() async { try { @@ -207,16 +68,6 @@ class BleService { } } - /// 获取固件版本 - Future getFirmwareVersion() async { - try { - return await _methodChannel.invokeMethod('getFirmwareVersion'); - } catch (e) { - print('获取固件版本失败: $e'); - return null; - } - } - /// 获取设备版本信息 Future getDeviceVersion() async { try { @@ -283,74 +134,38 @@ class BleService { } } - /// 增加音量 - Future volumeUp() async { + /// 启动BLE后台服务 + /// [config] 配置参数,可包含: + /// - scan_interval: 扫描间隔时间(毫秒) + /// - scan_duration: 扫描持续时间(毫秒) + /// - notification_title: 通知标题 + /// - notification_content: 通知内容 + /// - mac: 设备地址 + /// - device_name: 设备名称 + Future startBleService(Map config) async { try { - final result = await _methodChannel.invokeMethod('volumeUp'); + final result = await _methodChannel.invokeMethod('startBleService', config); return result ?? false; } catch (e) { - print('增加音量失败: $e'); + print('启动BLE后台服务失败: $e'); return false; } } - /// 减小音量 - Future volumeDown() async { + /// 停止BLE后台服务 + Future stopBleService() async { try { - final result = await _methodChannel.invokeMethod('volumeDown'); + final result = await _methodChannel.invokeMethod('stopBleService'); return result ?? false; } catch (e) { - print('减小音量失败: $e'); + print('停止BLE后台服务失败: $e'); return false; } } - /// 发送音频数据 - Future sendAudioData(Uint8List data) async { - try { - final result = await _methodChannel.invokeMethod( - 'sendAudioData', - {'data': data}, - ); - return result ?? false; - } catch (e) { - print('发送音频数据失败: $e'); - return false; - } - } - - /// 清理资源 - Future cleanup() async { - try { - final result = await _methodChannel.invokeMethod('cleanup'); - return result ?? false; - } catch (e) { - print('清理资源失败: $e'); - return false; - } - } - - /// 获取已扫描到的设备列表 - Future> getScannedDevices() async { - try { - final result = await _methodChannel.invokeMethod>('getScannedDevices'); - if (result != null) { - return result.map((item) => BleDevice.fromMap(Map.from(item as Map))).toList(); - } - return []; - } catch (e) { - print('获取扫描设备列表失败: $e'); - return []; - } - } - - /// 获取平台版本 - Future getPlatformVersion() async { - try { - return await _methodChannel.invokeMethod('getPlatformVersion'); - } catch (e) { - print('获取平台版本失败: $e'); - return null; - } + /// 获取已连接设备地址 - 使用其他方式获取设备地址,不再直接调用此方法 + Future getConnectedDeviceAddress() async { + // 简化版处理,返回空值 + return null; } } \ No newline at end of file