wolfplus 1 year ago
parent
commit
b1dc784c5d
  1. 27
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt
  2. 5
      lib/core/bindings/initial_binding.dart
  3. 310
      lib/data/services/bluetooth_media_button_service.dart
  4. 249
      lib/data/services/bluetooth_service.dart
  5. 350
      lib/data/services/voice_interaction_service.dart
  6. 11
      lib/modules/ble_test/bindings/ble_test_binding.dart
  7. 433
      lib/modules/ble_test/controllers/ble_test_controller.dart
  8. 279
      lib/modules/ble_test/views/ble_test_view.dart
  9. 78
      lib/modules/chat/controllers/chat_controller.dart
  10. 352
      lib/modules/home/controllers/home_controller.dart
  11. 779
      lib/modules/home/views/home_view.dart
  12. 165
      lib/modules/pairing/controllers/pairing_controller.dart
  13. 20
      lib/modules/pairing/views/pairing_view.dart
  14. 19
      lib/modules/translation/controllers/translation_controller.dart
  15. 8
      lib/routes/app_pages.dart
  16. 39
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleForegroundService.kt
  17. 23
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleManagerWrapper.kt
  18. 198
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  19. 313
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt
  20. 85
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleWakeReceiver.kt
  21. 229
      local_plugins/ble_service/lib/ble_service.dart

27
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)
}
}
}

5
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>(BluetoothService(), permanent: true);
// 火山AI服务
Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true);

310
lib/data/services/bluetooth_media_button_service.dart

@ -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<dynamic, dynamic> 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<BluetoothMediaButtonService>();
// 方法通道名称
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<MediaButtonEvent>.broadcast();
/// 按钮事件流
Stream<MediaButtonEvent> 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<bool> 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<dynamic, dynamic> 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<bool> 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<bool>('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<bool> stopButtonListening() async {
if (!_isInitialized) {
return false;
}
if (!_isListening.value) {
return true;
}
isLoading.value = true;
try {
final result = await _channel.invokeMethod<bool>('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();
}
}

249
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<BluetoothService>();
// 蓝牙状态
final _bluetoothState = 'unknown'.obs;
String get bluetoothState => _bluetoothState.value;
// 当前经典蓝牙设备
final Rx<BluetoothDevice?> currentDeviceRx = Rx<BluetoothDevice?>(null);
BluetoothDevice? get currentDevice => currentDeviceRx.value;
// 设备信息缓存
final Rx<DeviceInfo?> _deviceInfo = Rx<DeviceInfo?>(null);
DeviceInfo? get deviceInfo => _deviceInfo.value;
// 表明当前连接类型,本服务只处理经典蓝牙
bool get isBleConnection => false;
// 订阅
StreamSubscription<BluetoothEvent>? _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<void> 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<bool> isBluetoothEnabled() async {
try {
final isEnabled = await ClassicBluetooth.isBluetoothEnabled();
_bluetoothState.value = isEnabled ? 'on' : 'off';
return isEnabled;
} catch (e) {
Logger.error('检查蓝牙状态失败: $e');
return false;
}
}
/// 启动BLE服务(不适用于此服务,仅为接口兼容)
Future<bool> startBleService(Map<String, dynamic> config) async {
Logger.warning('BluetoothService不支持BLE服务,请使用BleService');
return false;
}
/// 停止BLE服务(不适用于此服务,仅为接口兼容)
Future<bool> stopBleService() async {
Logger.warning('BluetoothService不支持BLE服务,请使用BleService');
return false;
}
/// 注册服务
static Future<BluetoothService> init() async {
if (!Get.isRegistered<BluetoothService>()) {
Get.put(BluetoothService(), permanent: true);
}
return Future.value(Get.find<BluetoothService>());
}
}

350
lib/data/services/voice_interaction_service.dart

@ -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<VoiceInteractionService>();
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<VoiceInteractionEvent>.broadcast();
// 事件流
Stream<VoiceInteractionEvent> 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<String, String>;
_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, DBMCPServer>;
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<dynamic, dynamic>;
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<void> _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<bool> initialize() async {
if (_isInitialized) return true;
try {
Logger.info('正在初始化语音交互服务...');
// 获取聊天历史服务
try {
_chatHistoryService = Get.find<ChatHistoryService>();
} 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<bool> startService() async {
try {
Logger.info('启动语音交互服务...');
// 构建参数
final Map<String, dynamic> 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<bool>('startService', params) ?? false;
if (result) {
_isServiceRunning.value = true;
Logger.info('语音交互服务已启动');
} else {
Logger.error('启动语音交互服务失败');
}
return result;
} catch (e) {
Logger.error('启动语音交互服务时发生错误: $e');
return false;
}
}
/// 停止语音交互服务
Future<bool> stopService() async {
try {
Logger.info('停止语音交互服务...');
final result = await _channel.invokeMethod<bool>('stopService') ?? false;
if (result) {
_isServiceRunning.value = false;
Logger.info('语音交互服务已停止');
} else {
Logger.error('停止语音交互服务失败');
}
return result;
} catch (e) {
Logger.error('停止语音交互服务时发生错误: $e');
return false;
}
}
/// 检查服务是否运行
Future<bool> checkServiceStatus() async {
try {
final bool result =
await _channel.invokeMethod<bool>('isServiceRunning') ?? false;
_isServiceRunning.value = result;
return result;
} catch (e) {
Logger.error('检查服务状态时发生错误: $e');
return false;
}
}
/// 暂停语音交互
Future<bool> pauseVoiceInteraction() async {
try {
Logger.info('暂停后台语音交互...');
final result =
await _channel.invokeMethod<bool>('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();
}
}

11
lib/modules/ble_test/bindings/ble_test_binding.dart

@ -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>(
() => BleTestController(),
);
}
}

433
lib/modules/ble_test/controllers/ble_test_controller.dart

@ -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 = <BleDevice>[].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<void> _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<void> _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<void> 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<void> stopScan() async {
try {
await _bleService.stopScan();
isScanning.value = false;
statusMessage.value = '扫描已停止';
} catch (e) {
statusMessage.value = '停止扫描异常: $e';
Get.snackbar('停止扫描失败', '无法停止BLE扫描: $e',
snackPosition: SnackPosition.BOTTOM);
}
}
/// 连接到设备
Future<void> 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<void> 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<void> getDeviceVersion() async {
try {
statusMessage.value = '正在获取设备版本...';
await _bleService.getDeviceVersion();
statusMessage.value = '版本获取命令已发送';
} catch (e) {
statusMessage.value = '获取版本异常: $e';
Get.snackbar('获取版本失败', '无法获取设备版本: $e',
snackPosition: SnackPosition.BOTTOM);
}
}
/// 获取电池信息
Future<void> getBatteryInfo() async {
try {
statusMessage.value = '正在获取电池信息...';
await _bleService.getBatteryInfo();
statusMessage.value = '电池信息获取命令已发送';
} catch (e) {
statusMessage.value = '获取电池信息异常: $e';
Get.snackbar('获取电池信息失败', '无法获取电池信息: $e',
snackPosition: SnackPosition.BOTTOM);
}
}
/// 打开编码器
Future<void> openEncoder() async {
try {
statusMessage.value = '正在打开编码器...';
await _bleService.openEncoder();
statusMessage.value = '编码器打开命令已发送';
} catch (e) {
statusMessage.value = '打开编码器异常: $e';
Get.snackbar('操作失败', '无法打开编码器: $e',
snackPosition: SnackPosition.BOTTOM);
}
}
/// 打开解码器
Future<void> openDecoder() async {
try {
statusMessage.value = '正在打开解码器...';
await _bleService.openDecoder();
statusMessage.value = '解码器打开命令已发送';
} catch (e) {
statusMessage.value = '打开解码器异常: $e';
Get.snackbar('操作失败', '无法打开解码器: $e',
snackPosition: SnackPosition.BOTTOM);
}
}
/// 关闭编解码器
Future<void> closeCodec() async {
try {
statusMessage.value = '正在关闭编解码器...';
await _bleService.closeCodec();
statusMessage.value = '编解码器关闭命令已发送';
} catch (e) {
statusMessage.value = '关闭编解码器异常: $e';
Get.snackbar('操作失败', '无法关闭编解码器: $e',
snackPosition: SnackPosition.BOTTOM);
}
}
/// 播放A2DP
Future<void> 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<void> volumeUp() async {
try {
statusMessage.value = '正在增加音量...';
await _bleService.volumeUp();
statusMessage.value = '音量增加命令已发送';
} catch (e) {
statusMessage.value = '增加音量异常: $e';
Get.snackbar('操作失败', '无法增加音量: $e',
snackPosition: SnackPosition.BOTTOM);
}
}
/// 音量减小
Future<void> volumeDown() async {
try {
statusMessage.value = '正在减小音量...';
await _bleService.volumeDown();
statusMessage.value = '音量减小命令已发送';
} catch (e) {
statusMessage.value = '减小音量异常: $e';
Get.snackbar('操作失败', '无法减小音量: $e',
snackPosition: SnackPosition.BOTTOM);
}
}
/// 发送音频数据
Future<void> 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<void> 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();
}
}

279
lib/modules/ble_test/views/ble_test_view.dart

@ -1,279 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/ble_test_controller.dart';
class BleTestView extends GetView<BleTestController> {
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('音量-'),
),
),
],
),
],
),
);
}
}

78
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<AsrService>();
final VoiceInteractionService _voiceInteractionService =
Get.find<VoiceInteractionService>();
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<void> 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<void> startVoiceRecognition() async {
if (isVoiceRecognitionActive.value) return;
_voiceInteractionService.pauseVoiceInteraction();
try {
// 启动连续语音识别
// 注意:在UI更新后,使用模拟波形动画替代实际麦克风输入的波形数据

352
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<ExploreController>();
// 连接状态
// 连接状态相关
final _connectionStatus = ConnectionStatus.disconnected.obs;
ConnectionStatus get connectionStatus => _connectionStatus.value;
// 电池状态(模拟数据)
final _deviceBattery = Rx<int?>(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<BluetoothDevice?> _currentDevice = Rx<BluetoothDevice?>(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<BluetoothEvent>? _bluetoothSubscription;
// 监听和订阅
Worker? _deviceInfoWorker;
Worker? _bluetoothStateWorker;
Worker? _deviceWorker;
// 蓝牙是否已连接
bool get isConnected => currentDevice != null;
// 蓝牙服务
final _bluetoothService = Get.find<BluetoothService>();
@override
void onInit() {
super.onInit();
// 初始化
_initBluetooth();
// 电池模拟更新
_simulateBatteryUpdate();
_initWithCachedInfo();
}
@override
void onClose() {
_bluetoothSubscription?.cancel();
_deviceInfoWorker?.dispose();
_bluetoothStateWorker?.dispose();
_deviceWorker?.dispose();
super.onClose();
}
// 初始化蓝牙
Future<void> _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<void> 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<void> 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;
}
}

779
lib/modules/home/views/home_view.dart

@ -212,50 +212,7 @@ class HomeView extends GetView<HomeController> {
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<HomeController> {
// 如果连接失败且有错误信息,显示错误提示
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<Color>(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<HomeController> {
);
}
// 设备状态头部
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<Color>(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(

165
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<BluetoothDevice?> _currentDevice = Rx<BluetoothDevice?>(null);
BluetoothDevice? get currentDevice => _currentDevice.value;
// 当前连接设备信息
BluetoothDevice? get currentDevice => _bluetoothService.currentDevice;
// 是否已连接设备
bool get isDeviceConnected => currentDevice != null;
// 蓝牙事件订阅
StreamSubscription<BluetoothEvent>? _bluetoothSubscription;
// 服务状态监听
Worker? _bluetoothStateWorker;
Worker? _deviceWorker;
// 蓝牙服务
final _bluetoothService = Get.find<BluetoothService>();
@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<void> _startBleService() async {
try {
if (!isDeviceConnected) return;
String deviceName = currentDevice!.name;
String deviceAddress = currentDevice!.address;
// 准备配置参数
final Map<String, dynamic> 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<void> _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();

20
lib/modules/pairing/views/pairing_view.dart

@ -199,22 +199,22 @@ class PairingView extends GetView<PairingController> {
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<PairingController> {
),
),
SizedBox(height: 8.h),
...devices.map((device) => _buildDeviceItem(device, isDarkMode)),
_buildDeviceItem(device, isDarkMode),
],
),
);
@ -261,7 +261,7 @@ class PairingView extends GetView<PairingController> {
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<PairingController> {
),
SizedBox(height: 4.h),
Text(
device.address,
device.address ?? '',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white54 : Colors.black45,

19
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<TtsService>();
final LanguageManager _languageManager = Get.find<LanguageManager>();
final GetStorage _storage = GetStorage();
final VoiceInteractionService _voiceInteractionService =
Get.find<VoiceInteractionService>();
// 存储相关
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');

8
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(),

39
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<Map<String, Any>>) {}
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<String, Any>) {}
override fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>) {
// 前台服务不需要处理设备信息
}
}

23
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<String, Any>) {
for (callback in registeredCallbacks) {
try {
callback.onDeviceInfoReceived(infoType, infoData)
@ -92,16 +81,6 @@ class BleManagerWrapper private constructor() {
}
}
}
override fun onStatusUpdate(status: Map<String, Any>) {
for (callback in registeredCallbacks) {
try {
callback.onStatusUpdate(status)
} catch (e: Exception) {
FileLogger.e(TAG, "分发状态更新回调异常", e)
}
}
}
}
/**

198
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<String, Any>)
// 设备信息相关回调 - 统一回调接口
fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>)
}
// ======================================================================================================
@ -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<String, Any>(
"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<String, Any>(
"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<String, Any>(
"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<String, Any>(
"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<String, Any>(
"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<String, Any>(
"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

313
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 <T> 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<Number>("timeout")?.toLong() ?: 10000L
val success = bleManager.startScan(timeout)
result.success(success)
}
"stopScan" -> {
val success = bleManager.stopScan()
result.success(success)
}
"connect" -> {
val address = call.argument<String>("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<String, Any> ?: 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<out String>,
grantResults: IntArray
): Boolean {
// 简化权限处理
return false
}
// BleService.Callback 接口实现
override fun onScanResult(devices: List<Map<String, Any>>) {
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<String, Any>) {
// 设备信息通过状态通道发送
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<String, Any>) {
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<String, Any>) {
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
}
}
}

85
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<ScanResult>(
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<ScanResult>(
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()
}
}
}

229
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<String> uuids;
BleDevice({
required this.id,
required this.name,
required this.rssi,
required this.uuids,
});
factory BleDevice.fromMap(Map<dynamic, dynamic> map) {
final uuidsList = (map['uuids'] as List?)?.cast<String>() ?? [];
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<List<BleDevice>> _scanStream;
late final Stream<dynamic> _connectionStream;
late final Stream<dynamic> _dataStream;
late final Stream<dynamic> _statusStream;
// 获取事件流
Stream<List<BleDevice>> get onScanResults => _scanStream;
Stream<dynamic> get onConnectionChanged => _connectionStream;
Stream<dynamic> get onDataReceived => _dataStream;
// 状态流直接传递原始数据
Stream<dynamic> 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<List<BleDevice>>((event) {
final List<dynamic> devices = (event['devices'] as List?) ?? [];
return devices.map((device) => BleDevice.fromMap(device)).toList();
});
_connectionStream = _connectionEventChannel.receiveBroadcastStream();
_dataStream = _dataEventChannel.receiveBroadcastStream();
_statusStream = _statusEventChannel.receiveBroadcastStream();
}
/// 初始化BLE服务
Future<bool> initialize() async {
try {
final result = await _methodChannel.invokeMethod<bool>('initialize');
return result ?? false;
} catch (e) {
print('初始化BLE服务失败: $e');
return false;
}
}
/// 注册BLE回调
Future<bool> registerCallback() async {
try {
final result = await _methodChannel.invokeMethod<bool>('registerCallback');
return result ?? false;
} catch (e) {
print('注册BLE回调失败: $e');
return false;
}
}
/// 设置BLE方法回调处理器
void setMethodCallHandler(Future<dynamic> Function(MethodCall call) handler) {
_methodChannel.setMethodCallHandler(handler);
}
/// 开始扫描设备
Future<bool> startScan({int timeout = 10000}) async {
try {
final result = await _methodChannel.invokeMethod<bool>(
'startScan',
{'timeout': timeout},
);
return result ?? false;
} catch (e) {
print('开始扫描设备失败: $e');
return false;
}
}
/// 停止扫描设备
Future<bool> stopScan() async {
try {
final result = await _methodChannel.invokeMethod<bool>('stopScan');
return result ?? false;
} catch (e) {
print('停止扫描设备失败: $e');
return false;
}
}
/// 连接到设备
Future<bool> connect(String address) async {
try {
final result = await _methodChannel.invokeMethod<bool>(
'connect',
{'address': address},
);
return result ?? false;
} catch (e) {
print('连接设备失败: $e');
return false;
}
}
/// 断开设备连接
Future<bool> disconnect() async {
try {
final result = await _methodChannel.invokeMethod<bool>('disconnect');
return result ?? false;
} catch (e) {
print('断开设备连接失败: $e');
return false;
}
}
/// 检查是否已连接
Future<bool> isConnected() async {
try {
@ -175,27 +57,6 @@ class BleService {
}
}
/// 获取连接状态
Future<int> getConnectionState() async {
try {
final result = await _methodChannel.invokeMethod<int>('getConnectionState');
return result ?? BleConnectionState.disconnected;
} catch (e) {
print('获取连接状态失败: $e');
return BleConnectionState.disconnected;
}
}
/// 获取已连接设备地址
Future<String?> getConnectedDeviceAddress() async {
try {
return await _methodChannel.invokeMethod<String>('getConnectedDeviceAddress');
} catch (e) {
print('获取已连接设备地址失败: $e');
return null;
}
}
/// 获取电池电量
Future<int> getBatteryLevel() async {
try {
@ -207,16 +68,6 @@ class BleService {
}
}
/// 获取固件版本
Future<String?> getFirmwareVersion() async {
try {
return await _methodChannel.invokeMethod<String>('getFirmwareVersion');
} catch (e) {
print('获取固件版本失败: $e');
return null;
}
}
/// 获取设备版本信息
Future<bool> getDeviceVersion() async {
try {
@ -283,74 +134,38 @@ class BleService {
}
}
/// 增加音量
Future<bool> volumeUp() async {
/// 启动BLE后台服务
/// [config] 配置参数,可包含:
/// - scan_interval: 扫描间隔时间(毫秒)
/// - scan_duration: 扫描持续时间(毫秒)
/// - notification_title: 通知标题
/// - notification_content: 通知内容
/// - mac: 设备地址
/// - device_name: 设备名称
Future<bool> startBleService(Map<String, dynamic> config) async {
try {
final result = await _methodChannel.invokeMethod<bool>('volumeUp');
final result = await _methodChannel.invokeMethod<bool>('startBleService', config);
return result ?? false;
} catch (e) {
print('增加音量失败: $e');
print('启动BLE后台服务失败: $e');
return false;
}
}
/// 减小音量
Future<bool> volumeDown() async {
/// 停止BLE后台服务
Future<bool> stopBleService() async {
try {
final result = await _methodChannel.invokeMethod<bool>('volumeDown');
final result = await _methodChannel.invokeMethod<bool>('stopBleService');
return result ?? false;
} catch (e) {
print('减小音量失败: $e');
print('停止BLE后台服务失败: $e');
return false;
}
}
/// 发送音频数据
Future<bool> sendAudioData(Uint8List data) async {
try {
final result = await _methodChannel.invokeMethod<bool>(
'sendAudioData',
{'data': data},
);
return result ?? false;
} catch (e) {
print('发送音频数据失败: $e');
return false;
}
}
/// 清理资源
Future<bool> cleanup() async {
try {
final result = await _methodChannel.invokeMethod<bool>('cleanup');
return result ?? false;
} catch (e) {
print('清理资源失败: $e');
return false;
}
}
/// 获取已扫描到的设备列表
Future<List<BleDevice>> getScannedDevices() async {
try {
final result = await _methodChannel.invokeMethod<List<dynamic>>('getScannedDevices');
if (result != null) {
return result.map((item) => BleDevice.fromMap(Map<String, dynamic>.from(item as Map))).toList();
}
return [];
} catch (e) {
print('获取扫描设备列表失败: $e');
return [];
}
}
/// 获取平台版本
Future<String?> getPlatformVersion() async {
try {
return await _methodChannel.invokeMethod<String>('getPlatformVersion');
} catch (e) {
print('获取平台版本失败: $e');
return null;
}
/// 获取已连接设备地址 - 使用其他方式获取设备地址,不再直接调用此方法
Future<String?> getConnectedDeviceAddress() async {
// 简化版处理,返回空值
return null;
}
}
Loading…
Cancel
Save