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
FileLogger.init(applicationContext) FileLogger.init(applicationContext)
// 初始化BLE后台唤醒功能
// initBleBackgroundScan()
FileLogger.d(TAG, "初始化完成") FileLogger.d(TAG, "初始化完成")
} }
@ -68,33 +65,13 @@ class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
// 注册标准Flutter插件
GeneratedPluginRegistrant.registerWith(flutterEngine) GeneratedPluginRegistrant.registerWith(flutterEngine)
} }
override fun onDestroy() { override fun onDestroy() {
super.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 '../../data/services/volcano_ai_service.dart';
import '../../core/utils/logger.dart'; import '../../core/utils/logger.dart';
import '../../data/services/open_ai_service_adapter.dart'; import '../../data/services/open_ai_service_adapter.dart';
import '../../data/services/bluetooth_service.dart';
/// 初始绑定,用于管理全局依赖 /// 初始绑定,用于管理全局依赖
class InitialBinding extends Bindings { class InitialBinding extends Bindings {
@override @override
@ -32,6 +33,10 @@ class InitialBinding extends Bindings {
adapter.initialize(); adapter.initialize();
return adapter; return adapter;
}, fenix: true); }, fenix: true);
// 蓝牙服务
Get.put<BluetoothService>(BluetoothService(), permanent: true);
// 火山AI服务 // 火山AI服务
Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true); 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/providers/agent_provider.dart';
import '../../../data/models/agent.dart'; import '../../../data/models/agent.dart';
import '../../../data/services/asr_service.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'; import '../../../data/services/open_ai_service_adapter.dart';
class ChatController extends GetxController { class ChatController extends GetxController {
@ -28,8 +26,7 @@ class ChatController extends GetxController {
late AiService _aiService; late AiService _aiService;
late TtsService _ttsService; late TtsService _ttsService;
final AsrService _asrService = Get.find<AsrService>(); final AsrService _asrService = Get.find<AsrService>();
final VoiceInteractionService _voiceInteractionService =
Get.find<VoiceInteractionService>();
late SpotifService _spotifService; late SpotifService _spotifService;
// 添加ScrollController用于控制列表滚动 // 添加ScrollController用于控制列表滚动
@ -187,8 +184,6 @@ class ChatController extends GetxController {
// 自动停止计时器会在startVoiceRecognition中启动 // 自动停止计时器会在startVoiceRecognition中启动
} }
// 订阅VoiceInteractionService事件
_subscribeToVoiceInteractionEvents();
_getLocation(); _getLocation();
@ -216,73 +211,7 @@ class ChatController extends GetxController {
_amapFlutter.startLocation(); _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 @override
void onClose() { void onClose() {
@ -366,7 +295,6 @@ class ChatController extends GetxController {
Future<void> sendMessage(String text) async { Future<void> sendMessage(String text) async {
if (text.trim().isEmpty) return; if (text.trim().isEmpty) return;
_voiceInteractionService.pauseVoiceInteraction();
// 中断当前响应 // 中断当前响应
_interruptCurrentResponse("发送新消息,中断当前响应"); _interruptCurrentResponse("发送新消息,中断当前响应");
@ -630,7 +558,7 @@ class ChatController extends GetxController {
Timer(const Duration(milliseconds: 260), () async { Timer(const Duration(milliseconds: 260), () async {
if (isDebounce) return; if (isDebounce) return;
_voiceInteractionService.pauseVoiceInteraction();
// 中断当前响应 // 中断当前响应
_interruptCurrentResponse("发送新消息,中断当前响应"); _interruptCurrentResponse("发送新消息,中断当前响应");
try { try {
@ -691,7 +619,7 @@ class ChatController extends GetxController {
Future<void> startVoiceRecognition() async { Future<void> startVoiceRecognition() async {
if (isVoiceRecognitionActive.value) return; if (isVoiceRecognitionActive.value) return;
_voiceInteractionService.pauseVoiceInteraction();
try { try {
// 启动连续语音识别 // 启动连续语音识别
// 注意:在UI更新后,使用模拟波形动画替代实际麦克风输入的波形数据 // 注意:在UI更新后,使用模拟波形动画替代实际麦克风输入的波形数据

352
lib/modules/home/controllers/home_controller.dart

@ -4,9 +4,10 @@ import '../../../routes/app_routes.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../explore/controllers/explore_controller.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 { enum ConnectionStatus {
disconnected, // 未连接 disconnected, // 未连接
connecting, // 连接中 connecting, // 连接中
@ -14,199 +15,177 @@ enum ConnectionStatus {
failed // 连接失败 failed // 连接失败
} }
/// 主页控制器,管理设备连接状态和信息展示
class HomeController extends GetxController { class HomeController extends GetxController {
static HomeController get to => Get.find(); static HomeController get to => Get.find();
// 依赖的控制器
final ExploreController exploreController = Get.find<ExploreController>(); final ExploreController exploreController = Get.find<ExploreController>();
// 连接状态 // 连接状态相关
final _connectionStatus = ConnectionStatus.disconnected.obs; 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; final _connectionErrorMsg = ''.obs;
String get connectionErrorMsg => _connectionErrorMsg.value;
// 蓝牙状态和设备信息
final _bluetoothState = 'unknown'.obs; final _bluetoothState = 'unknown'.obs;
// Getter方法
ConnectionStatus get connectionStatus => _connectionStatus.value;
String get connectionErrorMsg => _connectionErrorMsg.value;
String get bluetoothState => _bluetoothState.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); // 设备详细信息的getter,从deviceInfo获取数据
BluetoothDevice? get currentDevice => _currentDevice.value; 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 @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
_initWithCachedInfo();
// 初始化
_initBluetooth();
// 电池模拟更新
_simulateBatteryUpdate();
} }
@override @override
void onClose() { void onClose() {
_bluetoothSubscription?.cancel(); _deviceInfoWorker?.dispose();
_bluetoothStateWorker?.dispose();
_deviceWorker?.dispose();
super.onClose(); super.onClose();
} }
// 初始化蓝牙 /// 使用缓存的蓝牙设备信息初始化
Future<void> _initBluetooth() async { void _initWithCachedInfo() {
try { // 使用蓝牙服务中的当前状态
_connectionStatus.value = ConnectionStatus.connecting; _bluetoothState.value = _bluetoothService.bluetoothState;
// 检查蓝牙状态 // 根据当前设备状态设置连接状态
final isEnabled = await ClassicBluetooth.isBluetoothEnabled(); if (currentDevice != null) {
_bluetoothState.value = isEnabled ? 'on' : 'off'; _connectionStatus.value = ConnectionStatus.connected;
_connectionErrorMsg.value = '';
if (!isEnabled) { Logger.info('主页加载缓存设备信息: ${currentDevice!.name}');
_connectionStatus.value = ConnectionStatus.disconnected; } else {
_connectionStatus.value = ConnectionStatus.disconnected;
if (_bluetoothState.value == 'off') {
_connectionErrorMsg.value = '蓝牙未开启'; _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() { void _setupStateListeners() {
_bluetoothSubscription?.cancel(); // 设备信息监听 - 设备信息更新时自动触发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) { _bluetoothStateWorker = ever(_bluetoothService.bluetoothState.obs, (String state) {
_bluetoothState.value = event.state!; _bluetoothState.value = state;
// 如果蓝牙关闭,清空设备信息 // 如果蓝牙关闭,更新错误信息
if (event.state == 'off') { if (state == 'off') {
_currentDevice.value = null; _connectionErrorMsg.value = '蓝牙未开启';
_connectionErrorMsg.value = '蓝牙未开启'; _connectionStatus.value = ConnectionStatus.disconnected;
} }
_updateConnectionStatus(); update();
} else if (event.type == 'deviceConnected' && event.device != null) { });
final newDevice = event.device!;
// 设备连接状态监听 - 直接监听设备变化
// 更新当前设备 _deviceWorker = ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) {
_currentDevice.value = newDevice; if (device != null) {
_connectionErrorMsg.value = ''; // 设备已连接
_updateConnectionStatus(); if (_connectionStatus.value != ConnectionStatus.connected) {
} else if (event.type == 'deviceDisconnected' && event.device != null) { _connectionStatus.value = ConnectionStatus.connected;
final disconnectedDevice = event.device!; _connectionErrorMsg.value = '';
// 如果断开的是当前设备,清除当前设备 Logger.info('主页检测到设备已连接: ${device.name}');
if (_currentDevice.value?.address == disconnectedDevice.address) {
_currentDevice.value = null;
_connectionErrorMsg.value = '设备已断开连接';
} }
} else if (_connectionStatus.value == ConnectionStatus.connected) {
// 设备已断开
_connectionStatus.value = ConnectionStatus.disconnected;
_connectionErrorMsg.value = '设备已断开连接';
_updateConnectionStatus(); Logger.info('主页检测到设备已断开连接');
} }
}, onError: (error) {
Logger.error('蓝牙事件流错误: $error'); update();
}); });
} }
// 更新连接状态 /// 刷新设备连接状态
void _updateConnectionStatus() { /// 注意:此方法不会主动刷新设备,只更新UI状态
if (bluetoothState == 'off') { Future<void> refreshDeviceConnection() async {
_connectionStatus.value = ConnectionStatus.disconnected; // 更新当前状态以反映UI变化
_deviceBattery.value = null; bool hasDevice = currentDevice != null;
update();
return; if (hasDevice) {
}
if (currentDevice != null) {
_connectionStatus.value = ConnectionStatus.connected; _connectionStatus.value = ConnectionStatus.connected;
_connectionErrorMsg.value = '';
Logger.info('使用缓存设备信息: ${currentDevice!.name}');
} else { } else {
if (_bluetoothState.value == 'off') {
_connectionErrorMsg.value = '蓝牙未开启';
} else {
_connectionErrorMsg.value = '未连接到设备';
}
_connectionStatus.value = ConnectionStatus.disconnected; _connectionStatus.value = ConnectionStatus.disconnected;
_deviceBattery.value = null;
} }
// 刷新UI
update(); 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() { void rescanDevices() {
Get.toNamed(Routes.pairing); Get.toNamed(Routes.pairing);
} }
// 打开翻译功能 /// 打开翻译功能
void openTranslation() { void openTranslation() {
Get.toNamed(Routes.translation); Get.toNamed(Routes.translation);
} }
// 打开AI伴侣功能 /// 打开AI伴侣功能
void openAIGirlfriend() { void openAIGirlfriend() {
exploreController.onAIFeatureTap(2); //进入AI女友页面 exploreController.onAIFeatureTap(2);
} }
// 展示AI功能 /// 展示AI功能
void exploreAIFeatures() { void exploreAIFeatures() {
Get.toNamed(Routes.chat); Get.toNamed(Routes.chat);
} }
// 获取连接状态对应的颜色 // UI辅助方法
/// 获取连接状态对应的颜色
Color getConnectionStatusColor() { Color getConnectionStatusColor() {
switch (_connectionStatus.value) { switch (_connectionStatus.value) {
case ConnectionStatus.connected: case ConnectionStatus.connected:
@ -221,51 +200,110 @@ class HomeController extends GetxController {
} }
} }
// 获取连接状态对应的文本 /// 获取连接状态对应的文本
String getConnectionStatusText() { String getConnectionStatusText() {
// 如果蓝牙关闭,优先显示蓝牙状态
if (bluetoothState == 'off') { if (bluetoothState == 'off') {
return 'bluetoothOff'.tr; // 蓝牙已关闭 return 'bluetoothOff'.tr;
} }
switch (_connectionStatus.value) { switch (_connectionStatus.value) {
case ConnectionStatus.connected: case ConnectionStatus.connected:
return 'connected'.tr; // 已连接 return 'connected'.tr;
case ConnectionStatus.connecting: case ConnectionStatus.connecting:
return 'connecting'.tr; // 连接中... return 'connecting'.tr;
case ConnectionStatus.failed: case ConnectionStatus.failed:
return 'connectionFailure'.tr; // 连接失败 return 'connectionFailure'.tr;
case ConnectionStatus.disconnected: case ConnectionStatus.disconnected:
default: default:
return 'unconnected'.tr; // 未连接 return 'unconnected'.tr;
} }
} }
// 获取设备名称 /// 获取设备名称
String getDeviceName() { String getDeviceName() {
return currentDevice?.name ?? 'unknownEquipment'.tr; // 未知设备 return currentDevice?.name ?? 'unknownEquipment'.tr;
} }
// 获取信号强度(模拟数据) /// 获取信号强度(模拟数据)
String getSignalStrength() { String getSignalStrength() {
if (!isConnected) { if (!isConnected) {
return 'zeroSignal'.tr; // 无信号 return 'zeroSignal'.tr;
} }
// 简单模拟信号强度
final random = DateTime.now().millisecondsSinceEpoch % 3; final random = DateTime.now().millisecondsSinceEpoch % 3;
switch (random) { switch (random) {
case 0: case 0:
return 'good'.tr; // 良好 return 'good'.tr;
case 1: case 1:
return 'fine'.tr; // 很好 return 'fine'.tr;
default: default:
return 'excellent'.tr; // 极佳 return 'excellent'.tr;
} }
} }
// 获取设备ID /// 获取设备ID
String getDeviceId() { 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( child: Column(
children: [ children: [
// 状态标题行 // 状态标题行
Row( _buildDeviceStatusHeader(isDarkMode),
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],
),
),
],
)),
],
),
// 设备信息 // 设备信息
SizedBox(height: 12.h), SizedBox(height: 12.h),
@ -263,233 +220,24 @@ class HomeView extends GetView<HomeController> {
// 如果连接失败且有错误信息,显示错误提示 // 如果连接失败且有错误信息,显示错误提示
if (controller.connectionStatus == ConnectionStatus.failed && if (controller.connectionStatus == ConnectionStatus.failed &&
controller.connectionErrorMsg.isNotEmpty) { controller.connectionErrorMsg.isNotEmpty) {
return Column( return _buildConnectionErrorSection(isDarkMode);
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), // 重新连接
),
],
);
} }
// 如果正在连接中,显示连接中提示 // 如果正在连接中,显示连接中提示
if (controller.connectionStatus == ConnectionStatus.connecting) { if (controller.connectionStatus == ConnectionStatus.connecting) {
return Container( return _buildConnectingIndicator();
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 controller.isConnected return controller.isConnected
? Column( ? _buildConnectedDeviceInfo(isDarkMode)
crossAxisAlignment: CrossAxisAlignment.start, : _buildDisconnectedInfo(isDarkMode);
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],
),
),
],
),
);
}), }),
// 连接按钮(仅在未连接时显示) // 连接按钮(仅在未连接时显示)
SizedBox(height: 12.h), SizedBox(height: 12.h),
Obx(() { Obx(() {
if (controller.connectionStatus == ConnectionStatus.disconnected) { if (controller.connectionStatus == ConnectionStatus.disconnected) {
return ElevatedButton( return _buildConnectButton();
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), // 连接设备
);
} else { } else {
return const SizedBox(); 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体验标题 // 热门AI体验标题
Widget _buildSectionTitle(bool isDarkMode) { Widget _buildSectionTitle(bool isDarkMode) {
return Row( 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 '../../../routes/app_routes.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import 'dart:io'; 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 { class PairingController extends GetxController {
@ -21,6 +22,7 @@ class PairingController extends GetxController {
// 蓝牙状态 // 蓝牙状态
final _bluetoothState = 'unknown'.obs; final _bluetoothState = 'unknown'.obs;
String get bluetoothState => _bluetoothState.value; String get bluetoothState => _bluetoothState.value;
bool get isBluetoothOn => _bluetoothState.value == 'on';
// 状态描述(用于UI显示) // 状态描述(用于UI显示)
final statusDescription = 'initializing'.tr.obs; final statusDescription = 'initializing'.tr.obs;
@ -28,12 +30,18 @@ class PairingController extends GetxController {
// 是否正在跳转到主页 // 是否正在跳转到主页
bool _isNavigatingToHome = false; bool _isNavigatingToHome = false;
// 当前连接设备 // 当前连接设备信息
final Rx<BluetoothDevice?> _currentDevice = Rx<BluetoothDevice?>(null); BluetoothDevice? get currentDevice => _bluetoothService.currentDevice;
BluetoothDevice? get currentDevice => _currentDevice.value;
// 是否已连接设备
bool get isDeviceConnected => currentDevice != null;
// 蓝牙事件订阅 // 服务状态监听
StreamSubscription<BluetoothEvent>? _bluetoothSubscription; Worker? _bluetoothStateWorker;
Worker? _deviceWorker;
// 蓝牙服务
final _bluetoothService = Get.find<BluetoothService>();
@override @override
void onInit() { void onInit() {
@ -43,7 +51,8 @@ class PairingController extends GetxController {
@override @override
void onClose() { void onClose() {
_bluetoothSubscription?.cancel(); _bluetoothStateWorker?.dispose();
_deviceWorker?.dispose();
super.onClose(); super.onClose();
} }
@ -59,12 +68,12 @@ class PairingController extends GetxController {
return; return;
} }
// 监听蓝牙状态变化 // 设置服务状态监听
_setupBluetoothListener(); _setupServiceStateListeners();
// 检查蓝牙状态 // 检查蓝牙状态
statusDescription.value = 'checkingBluetoothStatus'.tr; statusDescription.value = 'checkingBluetoothStatus'.tr;
final isEnabled = await ClassicBluetooth.isBluetoothEnabled(); final isEnabled = await _bluetoothService.isBluetoothEnabled();
_bluetoothState.value = isEnabled ? 'on' : 'off'; _bluetoothState.value = isEnabled ? 'on' : 'off';
if (!isEnabled) { if (!isEnabled) {
@ -127,49 +136,27 @@ class PairingController extends GetxController {
return 0; return 0;
} }
/// 设置蓝牙状态监听 /// 设置服务状态监听
void _setupBluetoothListener() { void _setupServiceStateListeners() {
_bluetoothSubscription?.cancel(); // 监听蓝牙服务的蓝牙状态变化
_bluetoothStateWorker = ever(_bluetoothService.bluetoothState.obs, (String state) {
_bluetoothState.value = state;
_updateStatusDescription();
// 如果蓝牙关闭,可能需要更新UI或提示用户
if (state == 'off') {
Logger.info('蓝牙已关闭');
}
});
try { // 监听当前设备变化 - 使用公开的currentDeviceRx字段
_bluetoothSubscription = ClassicBluetooth.events.listen( _deviceWorker = ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) {
(event) { if (device != null && !_isNavigatingToHome) {
if (event.type == 'stateChanged' && event.state != null) { Logger.info('配对页面发现设备已连接: ${device.name}');
_bluetoothState.value = event.state!; _autoNavigateToHome();
_updateStatusDescription(); }
_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');
}
} }
/// 更新状态描述 /// 更新状态描述
@ -184,7 +171,8 @@ class PairingController extends GetxController {
return; return;
} }
if (currentDevice != null) { if (isDeviceConnected) {
// 有设备连接时,显示设备名称
statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}'; statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}';
return; return;
} }
@ -208,15 +196,15 @@ class PairingController extends GetxController {
isLoading.value = true; isLoading.value = true;
statusDescription.value = 'searchingDevice'.tr; statusDescription.value = 'searchingDevice'.tr;
// 获取已连接的蓝牙设备 // 使用蓝牙服务刷新连接
final devices = await ClassicBluetooth.getConnectedDevices(); await _bluetoothService.refreshDeviceConnection();
// 如果有设备则设置为当前设备 // 检查连接状态
if (devices.isNotEmpty) { if (isDeviceConnected) {
_currentDevice.value = devices.first; Logger.info('刷新发现设备已连接: ${currentDevice!.name}');
_autoNavigateToHome(); _autoNavigateToHome();
} else { } else {
_currentDevice.value = null; Logger.info('刷新未发现已连接设备');
} }
} catch (e) { } catch (e) {
Logger.error('刷新设备失败: $e'); Logger.error('刷新设备失败: $e');
@ -229,22 +217,75 @@ class PairingController extends GetxController {
/// 自动跳转到主页 /// 自动跳转到主页
void _autoNavigateToHome() { void _autoNavigateToHome() {
if (_isNavigatingToHome || currentDevice == null) return; if (_isNavigatingToHome) return;
// 如果没有连接,不进行跳转
if (!isDeviceConnected) return;
_isNavigatingToHome = true; _isNavigatingToHome = true;
Logger.info('已找到连接的设备 ${currentDevice?.name},自动跳转到主页');
Logger.info('已找到连接的设备 ${currentDevice!.name},自动跳转到主页');
// 设备连接时启动BLE后台服务
_startBleService();
Future.delayed(const Duration(milliseconds: 500), () { Future.delayed(const Duration(milliseconds: 500), () {
Get.offAllNamed(Routes.home); 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() { void openBluetoothSettings() {
try { try {
if (!hasPermissions) { if (!hasPermissions) {
openAppSettings(); openAppSettings();
} else if (Platform.isAndroid && bluetoothState == 'off') { } else if (Platform.isAndroid && !isBluetoothOn) {
AppSettings.openAppSettings(type: AppSettingsType.bluetooth); AppSettings.openAppSettings(type: AppSettingsType.bluetooth);
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
openAppSettings(); openAppSettings();

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

@ -199,22 +199,22 @@ class PairingView extends GetView<PairingController> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
// 设备列表(有设备时才显示) // 当前连接设备(有设备时才显示)
if (controller.connectedDevices.isNotEmpty) ...[ if (controller.currentDevice != null) ...[
SizedBox(height: 24.h), SizedBox(height: 24.h),
_buildConnectedDevicesList(isDarkMode), _buildConnectedDevice(isDarkMode),
], ],
], ],
); );
}); });
} }
// 已连接设备列表 // 已连接设备信息
Widget _buildConnectedDevicesList(bool isDarkMode) { Widget _buildConnectedDevice(bool isDarkMode) {
return Obx(() { return Obx(() {
final devices = controller.connectedDevices; final device = controller.currentDevice;
if (devices.isEmpty) { if (device == null) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
@ -232,7 +232,7 @@ class PairingView extends GetView<PairingController> {
), ),
), ),
SizedBox(height: 8.h), 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, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
device.name, device.name ?? 'unknown'.tr,
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -270,7 +270,7 @@ class PairingView extends GetView<PairingController> {
), ),
SizedBox(height: 4.h), SizedBox(height: 4.h),
Text( Text(
device.address, device.address ?? '',
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: isDarkMode ? Colors.white54 : Colors.black45, 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 '../../../data/services/language_manager.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import '../../../data/models/events/voice_interaction_event.dart'; import '../../../data/models/events/voice_interaction_event.dart';
import '../../../data/services/voice_interaction_service.dart';
import '../../../data/services/asr_service.dart'; import '../../../data/services/asr_service.dart';
import 'package:jieli/jieli.dart'; import 'package:jieli/jieli.dart';
@ -39,8 +38,6 @@ class TranslationController extends GetxController {
final TtsService _ttsService = Get.find<TtsService>(); final TtsService _ttsService = Get.find<TtsService>();
final LanguageManager _languageManager = Get.find<LanguageManager>(); final LanguageManager _languageManager = Get.find<LanguageManager>();
final GetStorage _storage = GetStorage(); final GetStorage _storage = GetStorage();
final VoiceInteractionService _voiceInteractionService =
Get.find<VoiceInteractionService>();
// 存储相关 // 存储相关
static const String _historyKey = 'translation_history'; static const String _historyKey = 'translation_history';
@ -106,9 +103,7 @@ class TranslationController extends GetxController {
targetLanguageCode.value = targetAsrCode; targetLanguageCode.value = targetAsrCode;
} }
// 订阅原生服务事件
_subscribeToVoiceInteractionService();
// 加载历史记录 // 加载历史记录
loadTranslationHistory(); 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() { void _loadSavedLanguages() {
@ -209,7 +193,6 @@ class TranslationController extends GetxController {
Logger.info('开始语音识别'); Logger.info('开始语音识别');
if (isRecognizing.value) return; if (isRecognizing.value) return;
_voiceInteractionService.pauseVoiceInteraction();
try { try {
Logger.info('开始语音识别1'); 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/jieli_test/bindings/jieli_test_binding.dart';
import '../modules/opus_test/views/opus_test_view.dart'; import '../modules/opus_test/views/opus_test_view.dart';
import '../modules/opus_test/bindings/opus_test_binding.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/views/speech_test_view.dart';
import '../modules/speech_test/bindings/speech_test_binding.dart'; import '../modules/speech_test/bindings/speech_test_binding.dart';
@ -144,11 +142,7 @@ abstract class AppPages {
page: () => const OpusTestView(), page: () => const OpusTestView(),
binding: OpusTestBinding(), binding: OpusTestBinding(),
), ),
GetPage(
name: Routes.bleTest,
page: () => const BleTestView(),
binding: BleTestBinding(),
),
GetPage( GetPage(
name: Routes.speechTest, name: Routes.speechTest,
page: () => const SpeechTestView(), 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 { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
FileLogger.i(TAG, "服务onStartCommand: ${intent?.action}") FileLogger.i(TAG, "服务onStartCommand: ${intent?.action}")
// 1. 启动前台服务 // 1. 立即启动前台服务,这必须是第一步操作
startForegroundServiceWithNotification() startForegroundServiceWithNotification()
// 2. 注册后台扫描 // 添加延迟确保通知已显示
registerBackgroundScan() serviceScope.launch {
try {
// 3. 处理连接请求 // 2. 注册后台扫描
val macAddress = intent?.getStringExtra("mac") registerBackgroundScan()
if (!macAddress.isNullOrEmpty()) {
// 检查当前连接状态,如果未连接则发起连接 // 3. 处理连接请求
serviceScope.launch { val macAddress = intent?.getStringExtra("mac")
if (!bleManager.isConnected()) { if (!macAddress.isNullOrEmpty()) {
FileLogger.i(TAG, "开始连接到设备: $macAddress") // 检查当前连接状态,如果未连接则发起连接
connectToDevice(macAddress) if (!bleManager.isConnected()) {
} else { FileLogger.i(TAG, "开始连接到设备: $macAddress")
FileLogger.i(TAG, "设备已连接,忽略连接请求") 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 onScanResult(devices: List<Map<String, Any>>) {}
override fun onAudioDataReceived(data: ByteArray) {} override fun onAudioDataReceived(data: ByteArray) {}
override fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean) {} override fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>) {
override fun onDeviceInfoReceived(infoType: Int, infoData: String) {} // 前台服务不需要处理设备信息
override fun onStatusUpdate(status: 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) { for (callback in registeredCallbacks) {
try { try {
callback.onDeviceInfoReceived(infoType, infoData) 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 onAudioDataReceived(data: ByteArray)
fun onWakeupSignalReceived() fun onWakeupSignalReceived()
// 命令响应相关回调 // 设备信息相关回调 - 统一回调接口
fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean) fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>)
// 设备信息相关回调
fun onDeviceInfoReceived(infoType: Int, infoData: String)
// 状态更新相关回调
fun onStatusUpdate(status: Map<String, Any>)
} }
// ====================================================================================================== // ======================================================================================================
@ -347,9 +341,11 @@ class BleService(private val context: Context) {
} else { } else {
FileLogger.w(TAG, "音频服务特征未找到") FileLogger.w(TAG, "音频服务特征未找到")
} }
} }
override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) { 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 val data = c.value ?: return
// 根据特征UUID区分处理 // 根据特征UUID区分处理
@ -439,6 +435,7 @@ class BleService(private val context: Context) {
* 处理设备响应数据 - 帧头为0xBB * 处理设备响应数据 - 帧头为0xBB
*/ */
private fun processDeviceResponse(data: ByteArray) { private fun processDeviceResponse(data: ByteArray) {
FileLogger.d(TAG, "收到设备响应: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}")
if (data.size < 3) { if (data.size < 3) {
FileLogger.e(TAG, "设备响应数据格式错误:数据长度过短") FileLogger.e(TAG, "设备响应数据格式错误:数据长度过短")
return return
@ -450,7 +447,13 @@ class BleService(private val context: Context) {
// 判断命令失败响应(长度为0) // 判断命令失败响应(长度为0)
if (data.size == 4 && data[2].toInt() and 0xFF == 0) { if (data.size == 4 && data[2].toInt() and 0xFF == 0) {
FileLogger.w(TAG, "命令 0x${cmdType.toString(16)} 执行失败") 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 return
} }
@ -463,14 +466,13 @@ class BleService(private val context: Context) {
return return
} }
// 计算CRC校验 // 计算CRC校验 - 修正:计算除最后一个CRC字节外的所有字节
val crcData = ByteArray(2 + length) // 命令 + 长度 + 数据 val payload = data.sliceArray(0 until data.size - 1)
System.arraycopy(data, 1, crcData, 0, crcData.size) val calculatedCrc = crc8Maxim(payload, payload.size)
val calculatedCrc = crc8Maxim(crcData, crcData.size) val receivedCrc = data[data.size - 1].toInt() and 0xFF
val receivedCrc = data[data.size - 1]
if (calculatedCrc != receivedCrc) { if (calculatedCrc.toInt() and 0xFF != receivedCrc) {
FileLogger.e(TAG, "设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc.toInt() and 0xFF}") FileLogger.e(TAG, "设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}")
return return
} }
@ -485,45 +487,97 @@ class BleService(private val context: Context) {
// 根据命令类型处理不同的响应 // 根据命令类型处理不同的响应
when (cmdType) { when (cmdType) {
BleConst.CMD_GET_VERSION -> { BleConst.CMD_GET_VERSION -> {
val resultMap = mutableMapOf<String, Any>(
"success" to true,
"command" to cmdType,
"rawData" to responseData
)
if (responseData.isNotEmpty()) { if (responseData.isNotEmpty()) {
// 转换为字符串前,确认是否是有效的ASCII字符 // 转换为字符串前,确认是否是有效的ASCII字符
val version = responseData.joinToString(".") { (it.toInt() and 0xFF).toString() } val version = responseData.joinToString(".") { (it.toInt() and 0xFF).toString() }
firmwareVersion = version firmwareVersion = version
callback?.onDeviceInfoReceived(BleConst.CMD_GET_VERSION, version) resultMap["version"] = version
FileLogger.i(TAG, "接收到目标版本号: $version") FileLogger.i(TAG, "接收到目标版本号: $version")
} }
callback?.onProtocolCommandResult(cmdType, responseData, true)
callback?.onDeviceInfoReceived(cmdType, resultMap)
} }
BleConst.CMD_GET_PRODUCT_ID -> { 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) { if (responseData.isNotEmpty() && responseData.size >= 2) {
val productId = "0x${(responseData[0].toInt() and 0xFF).toString(16)}${(responseData[1].toInt() and 0xFF).toString(16)}" 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") 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 -> { BleConst.CMD_GET_COLOR_ID -> {
val resultMap = mutableMapOf<String, Any>(
"success" to true,
"command" to cmdType,
"rawData" to responseData
)
if (responseData.isNotEmpty()) { if (responseData.isNotEmpty()) {
val colorId = (responseData[0].toInt() and 0xFF).toString() val colorId = (responseData[0].toInt() and 0xFF).toString()
resultMap["colorId"] = colorId
FileLogger.i(TAG, "接收到耳机颜色ID: $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 -> { 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) { if (responseData.isNotEmpty() && responseData.size >= 3) {
val batteryLevel = responseData[0].toInt() and 0x7F // 处理电池信息 - 左耳、右耳、充电仓
val isCharging = (responseData[0].toInt() and 0x80) != 0 val leftEarBattery = responseData[0].toInt() and 0x7F
FileLogger.i(TAG, "接收到电量信息: ${batteryLevel}%, ${if(isCharging) "充电中" else "未充电"}") 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
// 更新电池电量 val caseBattery = if (responseData.size > 2) responseData[2].toInt() and 0x7F else 0
this.batteryLevel = batteryLevel 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 -> { BleConst.CMD_CONTROL_CODEC -> {
val resultMap = mutableMapOf<String, Any>(
"success" to true,
"command" to cmdType,
"rawData" to responseData
)
if (responseData.isNotEmpty() && responseData.size >= 2) { if (responseData.isNotEmpty() && responseData.size >= 2) {
val codecStatus = responseData[0].toInt() and 0xFF val codecStatus = responseData[0].toInt() and 0xFF
val channelMode = responseData[1].toInt() and 0xFF val channelMode = responseData[1].toInt() and 0xFF
@ -545,13 +599,26 @@ class BleService(private val context: Context) {
else -> "未知声道($channelMode)" else -> "未知声道($channelMode)"
} }
resultMap["codecStatus"] = codecStatus
resultMap["channelMode"] = channelMode
resultMap["statusDesc"] = statusDesc
resultMap["channelDesc"] = channelDesc
FileLogger.i(TAG, "编解码控制响应: $statusDesc, $channelDesc") FileLogger.i(TAG, "编解码控制响应: $statusDesc, $channelDesc")
} }
callback?.onProtocolCommandResult(cmdType, responseData, true)
callback?.onDeviceInfoReceived(cmdType, resultMap)
} }
else -> { else -> {
// 对于未知命令,返回原始数据
val resultMap = mapOf(
"success" to true,
"command" to cmdType,
"rawData" to responseData,
"isUnknown" to true
)
FileLogger.d(TAG, "收到未知命令响应: 0x${cmdType.toString(16)}") 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 * 处理设备主动上报数据 - 帧头为0xCC
*/ */
private fun processDeviceNotification(data: ByteArray) { private fun processDeviceNotification(data: ByteArray) {
FileLogger.d(TAG, "收到设备主动上报: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}")
if (data.size < 3) { if (data.size < 3) {
FileLogger.e(TAG, "设备主动上报数据格式错误:数据长度过短") FileLogger.e(TAG, "设备主动上报数据格式错误:数据长度过短")
return return
@ -575,14 +643,13 @@ class BleService(private val context: Context) {
return return
} }
// 计算CRC校验 // 计算CRC校验 - 修正:计算除最后一个CRC字节外的所有字节
val crcData = ByteArray(2 + length) // 类型 + 长度 + 数据 val payload = data.sliceArray(0 until data.size - 1)
System.arraycopy(data, 1, crcData, 0, crcData.size) val calculatedCrc = crc8Maxim(payload, payload.size)
val calculatedCrc = crc8Maxim(crcData, crcData.size) val receivedCrc = data[data.size - 1].toInt() and 0xFF
val receivedCrc = data[data.size - 1]
if (calculatedCrc != receivedCrc) { if (calculatedCrc.toInt() and 0xFF != receivedCrc) {
FileLogger.e(TAG, "设备主动上报CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc.toInt() and 0xFF}") FileLogger.e(TAG, "设备主动上报CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}")
return return
} }
@ -597,20 +664,56 @@ class BleService(private val context: Context) {
// 根据上报类型处理 // 根据上报类型处理
when (infoType) { when (infoType) {
BleConst.CMD_GET_BATTERY_INFO -> { // 电量信息 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) { if (notifyData.isNotEmpty() && notifyData.size >= 3) {
val batteryLevel = notifyData[0].toInt() and 0x7F // 处理电池信息 - 左耳、右耳、充电仓
val isCharging = (notifyData[0].toInt() and 0x80) != 0 val leftEarBattery = notifyData[0].toInt() and 0x7F
FileLogger.i(TAG, "接收到电量信息: ${batteryLevel}%, ${if(isCharging) "充电中" else "未充电"}") 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 -> { 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)}") 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) System.arraycopy(cmdData, 0, cmdPacket, 3, cmdLength)
} }
// 计算CRC - 对命令类型、长度和数据进行校验 // 计算CRC - 对帧头到数据的所有字节进行校验 (不包括CRC自身)
val crcData = ByteArray(2 + cmdLength) val crcPayload = cmdPacket.sliceArray(0 until cmdPacket.size - 1)
System.arraycopy(cmdPacket, 1, crcData, 0, crcData.size) val crc = crc8Maxim(crcPayload, crcPayload.size)
val crc = crc8Maxim(crcData, crcData.size)
// 设置CRC // 设置CRC
cmdPacket[3 + cmdLength] = 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 package com.yunqiinnovation.ble_service
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log
import androidx.annotation.NonNull import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityAware
@ -21,7 +21,7 @@ import com.yunqiinnovation.deepvoice_utils.FileLogger
* BleServicePlugin * BleServicePlugin
* 作为Flutter与原生BLE通信的插件接口 * 作为Flutter与原生BLE通信的插件接口
*/ */
class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.RequestPermissionsResultListener, BleService.Callback { class BleServicePlugin : FlutterPlugin, MethodCallHandler, BleService.Callback {
private val TAG = "BleServicePlugin" private val TAG = "BleServicePlugin"
// 添加主线程Handler // 添加主线程Handler
@ -30,9 +30,7 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin
// 方法通道 // 方法通道
private lateinit var methodChannel: MethodChannel private lateinit var methodChannel: MethodChannel
// 事件通道 // 事件通道 - 只保留数据和状态通道
private var scanEventChannel: EventChannel? = null
private var connectionEventChannel: EventChannel? = null
private var dataEventChannel: EventChannel? = null private var dataEventChannel: EventChannel? = null
private var statusEventChannel: EventChannel? = null private var statusEventChannel: EventChannel? = null
@ -42,9 +40,7 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin
// BleManager包装器 // BleManager包装器
private val bleManager = BleManagerWrapper.instance 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 dataEventSink: EventChannel.EventSink? = null
private var statusEventSink: 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 = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.ble_service")
methodChannel.setMethodCallHandler(this) methodChannel.setMethodCallHandler(this)
// 设置事件通道 // 设置事件通道 - 只保留必要的通道
setupEventChannels(flutterPluginBinding) setupEventChannels(flutterPluginBinding)
context = flutterPluginBinding.applicationContext context = flutterPluginBinding.applicationContext
@ -65,44 +61,14 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin
try { try {
bleManager.init(context) bleManager.init(context)
bleManager.addCallback(this) bleManager.addCallback(this)
// 开始定期状态更新
startPeriodicStatusUpdates()
FileLogger.d(TAG, "BleManagerWrapper initialized successfully") FileLogger.d(TAG, "BleManagerWrapper initialized successfully")
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e(TAG, "Failed to initialize BleManagerWrapper: ${e.message}", e) FileLogger.e(TAG, "Failed to initialize BleManagerWrapper: ${e.message}", e)
} }
} }
// 设置事件通道 // 设置事件通道 - 简化版
private fun setupEventChannels(binding: FlutterPlugin.FlutterPluginBinding) { 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 { dataEventChannel = EventChannel(binding.binaryMessenger, "com.yunqiinnovation.ble_service/data_events").apply {
setStreamHandler(object : EventChannel.StreamHandler { setStreamHandler(object : EventChannel.StreamHandler {
@ -121,8 +87,6 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin
setStreamHandler(object : EventChannel.StreamHandler { setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
statusEventSink = events statusEventSink = events
// 立即发送一次状态更新
sendStatusUpdate()
} }
override fun onCancel(arguments: Any?) { 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函数 // 添加sendEvent函数
private fun <T> sendEvent(eventSink: EventChannel.EventSink?, data: T, errorPrefix: String) { private fun <T> sendEvent(eventSink: EventChannel.EventSink?, data: T, errorPrefix: String) {
if (eventSink == null) return 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) { override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
methodChannel.setMethodCallHandler(null) methodChannel.setMethodCallHandler(null)
@ -216,8 +125,6 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin
bleManager.removeCallback(this) bleManager.removeCallback(this)
// 解除事件通道 // 解除事件通道
scanEventChannel?.setStreamHandler(null)
connectionEventChannel?.setStreamHandler(null)
dataEventChannel?.setStreamHandler(null) dataEventChannel?.setStreamHandler(null)
statusEventChannel?.setStreamHandler(null) statusEventChannel?.setStreamHandler(null)
} }
@ -226,59 +133,10 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin
FileLogger.d(TAG, "Method call: ${call.method}") FileLogger.d(TAG, "Method call: ${call.method}")
when (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" -> { "isConnected" -> {
val connected = bleManager.isConnected() val connected = bleManager.isConnected()
result.success(connected) result.success(connected)
} }
"getConnectionState" -> {
val state = bleManager.getConnectionState()
result.success(state)
}
"getConnectedDeviceAddress" -> {
val address = bleManager.getConnectedDeviceAddress()
result.success(address)
}
"getBatteryLevel" -> { "getBatteryLevel" -> {
val level = bleManager.getBatteryLevel() val level = bleManager.getBatteryLevel()
result.success(level) result.success(level)
@ -311,21 +169,28 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin
val success = bleManager.playA2DP() val success = bleManager.playA2DP()
result.success(success) result.success(success)
} }
"volumeUp" -> {
val success = bleManager.volumeUp() "startBleService" -> {
result.success(success) try {
} // 从Flutter获取配置参数
"volumeDown" -> { val configParams = call.arguments as? Map<String, Any> ?: mapOf()
val success = bleManager.volumeDown()
result.success(success) // 启动BLE服务并传递配置参数
} startBleServiceWithConfig(configParams)
"cleanup" -> { result.success(true)
bleManager.cleanup() } catch (e: Exception) {
result.success(true) FileLogger.e(TAG, "启动BLE服务失败", e)
result.error("BLE_SERVICE_ERROR", "启动BLE服务失败: ${e.message}", null)
}
} }
"getScannedDevices" -> { "stopBleService" -> {
val devices = bleManager.getScannedDevices() try {
result.success(devices) stopBleService()
result.success(true)
} catch (e: Exception) {
FileLogger.e(TAG, "停止BLE服务失败", e)
result.error("BLE_SERVICE_ERROR", "停止BLE服务失败: ${e.message}", null)
}
} }
else -> { else -> {
result.notImplemented() 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 接口实现 // BleService.Callback 接口实现
override fun onScanResult(devices: List<Map<String, Any>>) { override fun onScanResult(devices: List<Map<String, Any>>) {
sendEvent(scanEventSink, mapOf("devices" to devices), "发送扫描结果异常")
} }
override fun onConnectionStateChanged(state: Int) { 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) { 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() { override fun onWakeupSignalReceived() {
sendEvent(dataEventSink, mapOf("type" to "wakeup"), "发送唤醒信号异常") sendEvent(statusEventSink, mapOf("type" to "wakeup"), "发送唤醒信号异常")
} }
override fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>) {
override fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean) { // 设备信息通过状态通道发送
sendEvent(connectionEventSink, mapOf( val resultMap = infoData.toMutableMap()
"type" to "protocolCommandResult", resultMap["type"] = "deviceInfo"
"commandType" to commandType, resultMap["result"] = infoType
"data" to (data?.let { String(it) } ?: ""),
"success" to success
), "发送协议命令结果异常") // 发送设备信息事件
sendEvent(statusEventSink, resultMap, "发送设备信息异常")
} }
override fun onDeviceInfoReceived(infoType: Int, infoData: String) { /**
sendEvent(statusEventSink, mapOf( * 使用配置参数启动BLE服务
"type" to "deviceInfo", */
"infoType" to infoType, private fun startBleServiceWithConfig(configParams: Map<String, Any>) {
"infoData" to infoData try {
), "发送设备信息异常") // 初始化BleManagerWrapper
sendStatusUpdate() 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( * 停止BLE服务
"type" to "statusUpdate", */
"status" to status 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 android.os.Build
import com.yunqiinnovation.deepvoice_utils.FileLogger import com.yunqiinnovation.deepvoice_utils.FileLogger
import android.util.Log import android.util.Log
import android.os.Handler
import android.os.Looper
/** /**
* BLE唤醒接收器 * BLE唤醒接收器
* 当耳机发送含特定UUID广播时,系统会触发此接收器 * 当耳机发送含特定UUID广播时,系统会触发此接收器
@ -17,21 +19,72 @@ class BleWakeReceiver : BroadcastReceiver() {
} }
override fun onReceive(ctx: Context, intent: Intent) { override fun onReceive(ctx: Context, intent: Intent) {
FileLogger.init(ctx) // 获取PendingResult以防止接收器过早结束
FileLogger.d(TAG, "onReceive — 收到唤醒广播: action=${intent.action}, ${BleConst.ACTION_SCAN_RESULT}") val pendingResult = goAsync()
if (intent.action != BleConst.ACTION_SCAN_RESULT) return try {
FileLogger.init(ctx)
val list = intent.getParcelableArrayListExtra<ScanResult>( FileLogger.d(TAG, "onReceive — 收到唤醒广播: action=${intent.action}, ${BleConst.ACTION_SCAN_RESULT}")
BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT
)?: return if (intent.action != BleConst.ACTION_SCAN_RESULT) {
val mac = list.first().device.address pendingResult.finish()
FileLogger.d(TAG, "被系统唤醒,发现设备 $mac") return
}
// 拉起前台服务并把MAC传过去
val svc = Intent(ctx, BleForegroundService::class.java).putExtra("mac", mac) val list = intent.getParcelableArrayListExtra<ScanResult>(
svc.action = BleConst.ACTION_SCAN_RESULT BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) )?: run {
ctx.startForegroundService(svc) else ctx.startService(svc) 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; 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服务插件 /// BLE服务插件
class BleService { class BleService {
static final BleService _instance = BleService._internal(); static final BleService _instance = BleService._internal();
@ -51,119 +22,30 @@ class BleService {
// 方法通道 // 方法通道
final MethodChannel _methodChannel = const MethodChannel('com.yunqiinnovation.ble_service'); final MethodChannel _methodChannel = const MethodChannel('com.yunqiinnovation.ble_service');
// 事件通道 // 事件通道 - 简化为只使用数据和状态通道
late final EventChannel _scanEventChannel;
late final EventChannel _connectionEventChannel;
late final EventChannel _dataEventChannel; late final EventChannel _dataEventChannel;
late final EventChannel _statusEventChannel; late final EventChannel _statusEventChannel;
// 事件流 // 事件流
late final Stream<List<BleDevice>> _scanStream;
late final Stream<dynamic> _connectionStream;
late final Stream<dynamic> _dataStream; late final Stream<dynamic> _dataStream;
late final Stream<dynamic> _statusStream; late final Stream<dynamic> _statusStream;
// 获取事件流 // 获取事件流
Stream<List<BleDevice>> get onScanResults => _scanStream;
Stream<dynamic> get onConnectionChanged => _connectionStream;
Stream<dynamic> get onDataReceived => _dataStream; Stream<dynamic> get onDataReceived => _dataStream;
// 状态流直接传递原始数据
Stream<dynamic> get onStatusUpdated => _statusStream; Stream<dynamic> get onStatusUpdated => _statusStream;
// 内部构造函数 // 内部构造函数
BleService._internal() { 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'); _dataEventChannel = const EventChannel('com.yunqiinnovation.ble_service/data_events');
_statusEventChannel = const EventChannel('com.yunqiinnovation.ble_service/status_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(); _dataStream = _dataEventChannel.receiveBroadcastStream();
_statusStream = _statusEventChannel.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 { Future<bool> isConnected() async {
try { 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 { Future<int> getBatteryLevel() async {
try { 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 { Future<bool> getDeviceVersion() async {
try { try {
@ -283,74 +134,38 @@ class BleService {
} }
} }
/// 增加音量 /// 启动BLE后台服务
Future<bool> volumeUp() async { /// [config] 配置参数,可包含:
/// - scan_interval: 扫描间隔时间(毫秒)
/// - scan_duration: 扫描持续时间(毫秒)
/// - notification_title: 通知标题
/// - notification_content: 通知内容
/// - mac: 设备地址
/// - device_name: 设备名称
Future<bool> startBleService(Map<String, dynamic> config) async {
try { try {
final result = await _methodChannel.invokeMethod<bool>('volumeUp'); final result = await _methodChannel.invokeMethod<bool>('startBleService', config);
return result ?? false; return result ?? false;
} catch (e) { } catch (e) {
print('增加音量失败: $e'); print('启动BLE后台服务失败: $e');
return false; return false;
} }
} }
/// 减小音量 /// 停止BLE后台服务
Future<bool> volumeDown() async { Future<bool> stopBleService() async {
try { try {
final result = await _methodChannel.invokeMethod<bool>('volumeDown'); final result = await _methodChannel.invokeMethod<bool>('stopBleService');
return result ?? false; return result ?? false;
} catch (e) { } catch (e) {
print('减小音量失败: $e'); print('停止BLE后台服务失败: $e');
return false; return false;
} }
} }
/// 发送音频数据 /// 获取已连接设备地址 - 使用其他方式获取设备地址,不再直接调用此方法
Future<bool> sendAudioData(Uint8List data) async { Future<String?> getConnectedDeviceAddress() async {
try { // 简化版处理,返回空值
final result = await _methodChannel.invokeMethod<bool>( return null;
'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;
}
} }
} }
Loading…
Cancel
Save