You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1431 lines
48 KiB

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:get_storage/get_storage.dart';
import 'package:get/get.dart';
import 'package:ble_service/ble_service.dart';
import 'package:agent_service/agent_service.dart';
import '../models/user_Info.dart' as userInfo;
import '/data/models/appconfig_model.dart';
import '../../data/models/appconfig.dart';
import '../../core/translations/app_translations.dart';
import '../../core/utils/logger.dart';
import 'bluetooth_manager.dart';
import 'language_manager.dart';
import 'network/dio_manager.dart';
import '../../routes/app_routes.dart';
// 重新导出BLE连接状态常量,让外部可以通过BleManager访问
export 'package:ble_service/ble_service.dart' show BleConnectionState;
/// 命令类型常量(与原生层保持一致)
class BleCommands {
static const int GET_VERSION = 0x01;
static const int GET_PRODUCT_ID = 0x02;
static const int GET_PRODUCT_COLOR = 0x03;
static const int GET_BATTERY_INFO = 0x04;
static const int CONTROL_CODEC = 0x05;
static const int GET_WAKEUP_VERSION = 0x08;
static const int GET_VOICE_WAKEUP_STATUS = 0x14;
static const int SET_VOICE_WAKEUP_STATUS = 0x15;
/// 设备上报开启通话翻译 0x16 (帧头0xCC, cmdLen=0x01, status=0xFF)
static const int CALL_TRANSLATION_ON = 0x16;
/// 设备上报关闭通话翻译 0x17 (帧头0xCC, cmdLen=0x01, status=0xFF)
static const int CALL_TRANSLATION_OFF = 0x17;
}
/// 判断编解码控制指令是否成功
///
/// 根据 rawData 第二字节是否为非 0 来判断:
/// - 成功:第二字节不为 0
/// - 失败或无法解析:返回 false
bool _isCodecControlSuccess(Map<dynamic, dynamic> data) {
final dynamic raw = data['rawData'];
if (raw == null) return false;
int? secondByte;
if (raw is List) {
if (raw.length >= 2) {
final s = raw[1];
if (s is int) {
secondByte = s;
} else if (s is num) {
secondByte = s.toInt();
} else if (s is String) {
final t = s.trim();
if (t.startsWith('0x') || t.startsWith('0X')) {
secondByte = int.tryParse(t.substring(2), radix: 16);
} else {
secondByte = int.tryParse(t);
}
}
}
} else if (raw is String) {
final normalized = raw.trim().replaceAll('[', '').replaceAll(']', '');
final parts =
normalized.split(RegExp(r'[,\s]+')).where((e) => e.isNotEmpty).toList();
if (parts.length >= 2) {
final t = parts[1].trim();
if (t.startsWith('0x') || t.startsWith('0X')) {
secondByte = int.tryParse(t.substring(2), radix: 16);
} else {
secondByte = int.tryParse(t);
}
}
} else if (raw is Map) {
final dynamic s = raw['second'] ?? raw['status2'] ?? raw['result'];
if (s is int) {
secondByte = s;
} else if (s is num) {
secondByte = s.toInt();
} else if (s is String) {
final t = s.trim();
if (t.startsWith('0x') || t.startsWith('0X')) {
secondByte = int.tryParse(t.substring(2), radix: 16);
} else {
secondByte = int.tryParse(t);
}
}
} else {
// 无法解析的类型
return false;
}
return secondByte != null && secondByte != 0;
}
/// 蓝牙设备信息缓存模型
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 firmwareProductId;
final String firmwareProductColor;
final DateTime lastUpdated;
//唤醒词版本
final String wakeupLeftVersion;
final String wakeupRightVersion;
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.firmwareProductId = '',
this.firmwareProductColor = '',
DateTime? lastUpdated,
this.wakeupLeftVersion = '',
this.wakeupRightVersion = '',
}) : 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? firmwareProductId,
String? firmwareProductColor,
String? wakeupLeftVersion,
String? wakeupRightVersion,
}) {
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,
firmwareProductId: firmwareProductId ?? this.firmwareProductId,
firmwareProductColor: firmwareProductColor ?? this.firmwareProductColor,
lastUpdated: DateTime.now(),
wakeupLeftVersion: wakeupLeftVersion ?? this.wakeupLeftVersion,
wakeupRightVersion: wakeupRightVersion ?? this.wakeupRightVersion,
);
}
@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, '
'wakeupLeftVersion: $wakeupLeftVersion, '
'wakeupRightVersion: $wakeupRightVersion)';
}
}
/// BLE管理器 - 专门处理BLE设备连接和信息管理
class BleManager extends GetxService {
static BleManager get to => Get.find<BleManager>();
// BLE服务实例
final BleService _bleService = BleService();
// 经典蓝牙实例
// 当前连接的设备信息
String? currentDeviceAddress;
String? _currentDeviceName;
// BLE连接状态
final _connectionState = BleConnectionState.disconnected.obs;
int get connectionState => _connectionState.value;
// 公开的连接状态流,供外部监听
RxInt get connectionStateRx => _connectionState;
// 连接状态监听
final _isCall = false.obs;
final _isConnected = false.obs;
bool get isConnected => _isConnected.value;
bool get isCall => _isCall.value;
// 设备信息缓存
final deviceInfo = Rx<DeviceInfo?>(null); // 使用 Rx 包装
//添加编码器状态跟踪
final _isCodecActive = false.obs;
bool get isCodecActive => _isCodecActive.value;
RxBool get isCodecActiveRx => _isCodecActive;
bool _awaitingCodecOpen = false; //是否正在等待编码器打开的成功应答
bool _awaitingCodecClose = false; //是否正在等待编码器关闭的成功应答
// 语音唤醒状态
final _voiceWakeupEnabled = true.obs; // 唤醒开关状态,默认开启
bool get voiceWakeupEnabled => _voiceWakeupEnabled.value;
RxBool get voiceWakeupEnabledRx => _voiceWakeupEnabled;
final _voiceWakeupSupported = false.obs; // 耳机是否支持语音唤醒功能
bool get voiceWakeupSupported => _voiceWakeupSupported.value;
RxBool get voiceWakeupSupportedRx => _voiceWakeupSupported;
// 订阅
StreamSubscription<dynamic>? _statusSubscription;
StreamSubscription<dynamic>? _dataSubscription;
final GetStorage _storage = GetStorage();
final _wakeupVoiceUpgradeProgress = 0.obs; // 唤醒语音升级进度
RxInt get wakeupVoiceUpgradeProgress => _wakeupVoiceUpgradeProgress;
final _wakeupVoiceUpgradeStatus =
0.obs; // 唤醒语音升级状态 0: 未升级 1: 升级中 2: 升级成功 3: 升级失败
RxInt get wakeupVoiceUpgradeStatus => _wakeupVoiceUpgradeStatus;
/// 设备上报通话翻译开关信号流
/// true = 设备上报开启通话翻译 (0x16),false = 设备上报关闭通话翻译 (0x17)
/// 外部(如 TranslationController)可监听该流,收到信号后启动/停止通话翻译功能。
final StreamController<bool> _callTranslationRequestController =
StreamController<bool>.broadcast();
Stream<bool> get onCallTranslationRequest =>
_callTranslationRequestController.stream;
/// 初始化
@override
void onInit() {
super.onInit();
Logger.info('BLE管理器初始化');
// 设置BLE事件监听
_setupBleListener();
}
/// 释放资源
@override
void onClose() {
_statusSubscription?.cancel();
_dataSubscription?.cancel();
_callTranslationRequestController.close();
super.onClose();
}
/// 设置BLE事件监听
void _setupBleListener() {
_statusSubscription?.cancel();
_dataSubscription?.cancel();
try {
_statusSubscription = _bleService.onStatusUpdated.listen(
(event) {
if (event is! Map) return;
if (event['type'] == 'connectionStateChanged') {
final state = event['state'] as int;
final oldState = _connectionState.value;
_connectionState.value = state;
Logger.i('BleManager', 'BLE状态更新: $oldState → $state');
if (state == BleConnectionState.connected) {
_isConnected.value = true;
} else if (state == BleConnectionState.disconnected ||
state == BleConnectionState.error) {
_isConnected.value = false;
_awaitingCodecOpen = false;
_awaitingCodecClose = false;
_isCodecActive.value = false;
_voiceWakeupSupported.value = false;
_voiceWakeupEnabled.value = true;
Logger.i('BleManager',
'[VoiceWakeup] 断开连接,重置 supported=false, enabled=true');
_updateDeviceInfoIfNeeded(
batteryLevel: 0,
leftEarBattery: 0,
rightEarBattery: 0,
caseBattery: 0,
leftEarCharging: false,
rightEarCharging: false,
caseCharging: false,
firmwareVersion: '',
firmwareProductId: '',
firmwareProductColor: '',
wakeupLeftVersion: '',
wakeupRightVersion: '',
);
}
}
if (event['type'] == 'wakeupVoiceUpgradeProgress') {
_wakeupVoiceUpgradeProgress.value = event['progress'] as int? ?? 0;
_wakeupVoiceUpgradeStatus.value = 1;
}
if (event['type'] == 'wakeupVoiceUpgradeSuccess') {
_wakeupVoiceUpgradeStatus.value = 2;
Logger.i('BleManager', '唤醒语音升级成功');
}
if (event['type'] == 'wakeupVoiceUpgradeFailure') {
_wakeupVoiceUpgradeStatus.value = 3;
Logger.w('BleManager', '唤醒语音升级失败');
}
_processDeviceInfo(event);
},
onError: (error) {
Logger.e('BleManager', 'BLE状态事件流错误: $error');
},
);
} catch (e) {
Logger.e('BleManager', '设置BLE事件监听器失败: ${e.toString()}');
}
}
/// 处理来自原生层的设备信息
void _processDeviceInfo(Map<dynamic, dynamic> event) {
try {
if (!event.containsKey('command') || !event.containsKey('success')) {
return;
}
final bool success = event['success'] as bool;
if (!success) {
Logger.w('BleManager', '设备命令执行失败: ${event['command']}');
return;
}
final command = event['command'];
if (command is int) {
_processDeviceInfoByCommandId(command, event);
} else if (command is String) {
_processDeviceInfoByCommandName(command, event);
} else {
Logger.w('BleManager', '未知的命令类型: ${command.runtimeType}');
}
} catch (e, s) {
Logger.e('BleManager', '处理设备信息异常: $e', e, s);
}
}
/// 根据命令ID处理设备信息
void _processDeviceInfoByCommandId(
int commandId, Map<dynamic, dynamic> data) {
switch (commandId) {
case BleCommands.GET_VERSION:
_updateVersionInfoFromData(data);
break;
case BleCommands.GET_PRODUCT_ID:
_updateProductIdFromData(data);
break;
case BleCommands.GET_PRODUCT_COLOR:
break;
case BleCommands.GET_BATTERY_INFO:
_updateBatteryInfoFromData(data);
break;
case BleCommands.CONTROL_CODEC:
final bool codecSuccess = _isCodecControlSuccess(data);
if (_awaitingCodecOpen) {
_isCodecActive.value = codecSuccess;
_awaitingCodecOpen = false;
} else if (_awaitingCodecClose) {
if (codecSuccess) {
_isCodecActive.value = false;
}
_awaitingCodecClose = false;
}
if (!codecSuccess) {
Logger.w('BleManager', '编解码控制失败');
}
break;
case BleCommands.GET_WAKEUP_VERSION:
_updateWakeupVersionFromData(data);
break;
case BleCommands.GET_VOICE_WAKEUP_STATUS:
_updateVoiceWakeupStatusFromData(data);
break;
case BleCommands.SET_VOICE_WAKEUP_STATUS:
_updateVoiceWakeupSetResultFromData(data);
break;
case BleCommands.CALL_TRANSLATION_ON:
_handleCallTranslationRequest(true, data);
break;
case BleCommands.CALL_TRANSLATION_OFF:
_handleCallTranslationRequest(false, data);
break;
default:
Logger.w('BleManager', '未处理的命令ID: $commandId');
}
}
/// 处理设备上报的通话翻译开关指令 (0x16 / 0x17)
/// - [enable] true=开启通话翻译, false=关闭
/// 行为:
/// 1. 打印 [CALL_TRANS_CMD] 日志;
/// 2. 向 [onCallTranslationRequest] 流广播信号(TranslationController 监听并启停);
/// 3. 未在翻译页时,自动导航到 Routes.translation 并以 call 模式 + autoStart 进入。
void _handleCallTranslationRequest(
bool enable, Map<dynamic, dynamic> data) {
final status = data['status'];
Logger.i('BleManager',
'[CALL_TRANS_CMD] 收到设备上报${enable ? "开启" : "关闭"}通话翻译 cmd=0x${(enable ? BleCommands.CALL_TRANSLATION_ON : BleCommands.CALL_TRANSLATION_OFF).toRadixString(16)} status=$status');
// 广播给监听者(如 TranslationController 已在 call 模式可直接启/停)
if (!_callTranslationRequestController.isClosed) {
_callTranslationRequestController.add(enable);
}
// 开启:若当前不在翻译页,则导航并请求自动开始
if (enable) {
final currentRoute = Get.currentRoute;
if (currentRoute != Routes.translation) {
Logger.i('BleManager',
'[CALL_TRANS_CMD] 当前路由=$currentRoute,导航到翻译页(call 模式, autoStart=true)');
Get.toNamed(
Routes.translation,
arguments: {'translationId': 'call', 'autoStart': true},
);
} else {
Logger.i('BleManager',
'[CALL_TRANS_CMD] 已在翻译页,由 TranslationController 监听信号自行启动');
}
}
}
/// 根据命令名称处理设备信息
void _processDeviceInfoByCommandName(
String commandName, Map<dynamic, dynamic> data) {
switch (commandName) {
case 'batteryInfo':
_updateBatteryInfoFromData(data);
break;
case 'versionInfo':
_updateVersionInfoFromData(data);
break;
case 'productId':
_updateProductIdFromData(data);
break;
case 'productColor':
break;
case 'Error':
Logger.w('BleManager', '收到 Error 命令');
break;
default:
Logger.w('BleManager', '未处理的命令名称: $commandName');
}
}
/// 更新电池信息
void _updateBatteryInfoFromData(Map<dynamic, dynamic> data) {
try {
if (!data.containsKey('leftEarBattery') ||
!data.containsKey('rightEarBattery') ||
!data.containsKey('caseBattery')) {
Logger.w('BleManager', '电池信息数据不完整');
return;
}
final int leftBattery = (data['leftEarBattery'] as int? ?? 0) == 127
? -1
: (data['leftEarBattery'] as int? ?? 0) > 100
? 0
: (data['leftEarBattery'] as int? ?? 0);
final int rightBattery = (data['rightEarBattery'] as int? ?? 0) == 127
? -1
: (data['rightEarBattery'] as int? ?? 0) > 100
? 0
: (data['rightEarBattery'] as int? ?? 0);
final int caseBattery = (data['caseBattery'] as int? ?? 0) == 127
? -1
: (data['caseBattery'] as int? ?? 0) > 100
? 0
: (data['caseBattery'] as int? ?? 0);
final bool leftCharging = data['leftEarCharging'] as bool? ?? false;
final bool rightCharging = data['rightEarCharging'] as bool? ?? false;
final bool caseCharging = data['caseCharging'] as bool? ?? false;
final int overallBattery = data['batteryLevel'] as int? ?? leftBattery;
_updateDeviceInfoIfNeeded(
batteryLevel: overallBattery,
leftEarBattery: leftBattery,
rightEarBattery: rightBattery,
caseBattery: caseBattery,
leftEarCharging: leftCharging,
rightEarCharging: rightCharging,
caseCharging: caseCharging,
);
} catch (e, s) {
Logger.e('BleManager', '更新电池信息失败: $e', e, s);
}
}
/// 更新版本信息
void _updateVersionInfoFromData(Map<dynamic, dynamic> data) {
try {
// 确保数据中包含版本信息
if (!data.containsKey('version')) {
Logger.warning('版本信息数据不完整');
return;
}
// 提取版本信息
final String version = data['version'] as String? ?? '';
// 更新设备信息
_updateDeviceInfoIfNeeded(
firmwareVersion: version,
);
Logger.info('设备版本信息已更新: 版本=$version');
} catch (e) {
Logger.error('更新版本信息失败: ${e.toString()}');
}
}
/// 更新设备id,并检测是否为移动精灵设备
void _updateProductIdFromData(Map<dynamic, dynamic> data) {
try {
if (!data.containsKey('productId')) {
Logger.warning('版本信息数据不完整');
return;
}
final String productId = data['productId'] as String? ?? '';
_updateDeviceInfoIfNeeded(firmwareProductId: productId);
Logger.info('设备设备id已更新: 设备id=$productId');
} catch (e) {
Logger.error('更新设备id失败: ${e.toString()}');
}
}
/// 更新唤醒语音版本信息
void _updateWakeupVersionFromData(Map<dynamic, dynamic> data) {
try {
// 确保数据中包含版本信息
if (!data.containsKey('leftVersionDec') ||
!data.containsKey('rightVersionDec')) {
Logger.warning('唤醒语音版本信息数据不完整');
return;
}
// 提取版本信息
final String wakeupLeftVersion = data['leftVersionDec'] as String? ?? '';
final String wakeupRightVersion =
data['rightVersionDec'] as String? ?? '';
// 更新设备信息
_updateDeviceInfoIfNeeded(
wakeupLeftVersion: wakeupLeftVersion,
wakeupRightVersion: wakeupRightVersion,
);
Logger.info('设备唤醒语音版本信息已更新: 左=$wakeupLeftVersion, 右=$wakeupRightVersion');
} catch (e) {
Logger.error('更新唤醒语音版本信息失败: ${e.toString()}');
}
}
/// 更新语音唤醒状态(0x14 查询响应)
void _updateVoiceWakeupStatusFromData(Map<dynamic, dynamic> data) {
try {
if (!data.containsKey('voiceWakeupEnabled')) {
Logger.warning('[VoiceWakeup] 查询响应数据不完整,缺少 voiceWakeupEnabled');
return;
}
final bool enabled = data['voiceWakeupEnabled'] as bool? ?? true;
_voiceWakeupEnabled.value = enabled;
_voiceWakeupSupported.value = true;
Logger.info('[VoiceWakeup] 查询成功 → supported=true, enabled=${enabled ? "开启" : "关闭"}');
} catch (e) {
Logger.error('[VoiceWakeup] 解析查询响应异常: ${e.toString()}');
}
}
/// 处理设置语音唤醒的响应(0x15 设置响应)
void _updateVoiceWakeupSetResultFromData(Map<dynamic, dynamic> data) {
try {
final bool setSuccess = data['voiceWakeupSetSuccess'] as bool? ?? false;
if (setSuccess) {
Logger.info('[VoiceWakeup] 设置成功 → enabled=${_voiceWakeupEnabled.value ? "开启" : "关闭"}');
} else {
Logger.warning('[VoiceWakeup] 设置失败 → supported=false,开关置灰');
_voiceWakeupSupported.value = false;
}
} catch (e) {
Logger.error('[VoiceWakeup] 解析设置响应异常: ${e.toString()}');
}
}
/// 查询语音唤醒状态
Future<bool> queryVoiceWakeupStatus() async {
Logger.info('[VoiceWakeup] 发送查询命令(0x14)...');
final result = await _bleService.getVoiceWakeupStatus();
if (!result) {
_voiceWakeupSupported.value = false;
Logger.warning('[VoiceWakeup] 查询命令发送失败 → supported=false,设备可能不支持');
} else {
Logger.info('[VoiceWakeup] 查询命令发送成功,等待设备响应...');
}
return result;
}
/// 设置语音唤醒状态
Future<bool> setVoiceWakeupStatus(bool enabled) async {
Logger.info('[VoiceWakeup] 发送设置命令(0x15), enabled=${enabled ? "开启" : "关闭"}...');
_voiceWakeupEnabled.value = enabled; // 乐观更新
final result = await _bleService.setVoiceWakeupStatus(enabled);
if (!result) {
_voiceWakeupSupported.value = false;
Logger.warning('[VoiceWakeup] 设置命令发送失败 → supported=false,开关置灰');
} else {
Logger.info('[VoiceWakeup] 设置命令发送成功,等待设备响应...');
}
return result;
}
/// 更新设备信息(如果已存在设备)
void _updateDeviceInfoIfNeeded({
int? batteryLevel,
int? leftEarBattery,
int? rightEarBattery,
int? caseBattery,
bool? leftEarCharging,
bool? rightEarCharging,
bool? caseCharging,
String? firmwareVersion,
String? firmwareProductId,
String? firmwareProductColor,
String? wakeupLeftVersion,
String? wakeupRightVersion,
}) {
// 如果设备信息已存在,则更新
if (deviceInfo.value != null) {
deviceInfo.value = deviceInfo.value!.copyWith(
batteryLevel: batteryLevel ?? deviceInfo.value?.batteryLevel,
leftEarBattery: _getValidBatteryValue(
leftEarBattery, deviceInfo.value?.leftEarBattery),
rightEarBattery: _getValidBatteryValue(
rightEarBattery, deviceInfo.value?.rightEarBattery),
caseBattery: caseBattery ?? deviceInfo.value?.caseBattery,
leftEarCharging: leftEarCharging ?? deviceInfo.value!.leftEarCharging,
rightEarCharging:
rightEarCharging ?? deviceInfo.value!.rightEarCharging,
caseCharging: caseCharging ?? deviceInfo.value!.caseCharging,
firmwareVersion: firmwareVersion ?? deviceInfo.value!.firmwareVersion,
firmwareProductId:
firmwareProductId ?? deviceInfo.value!.firmwareProductId,
firmwareProductColor:
firmwareProductColor ?? deviceInfo.value!.firmwareProductColor,
wakeupLeftVersion:
wakeupLeftVersion ?? deviceInfo.value!.wakeupLeftVersion,
wakeupRightVersion:
wakeupRightVersion ?? deviceInfo.value!.wakeupRightVersion,
);
}
// 创建设备信息
// 如果设备信息不存在,但有地址和名称,则创建新的设备信息
else if (deviceInfo.value == null &&
_isConnected.value &&
(firmwareVersion != null || batteryLevel != null)) {
String deviceAddress = currentDeviceAddress ?? "";
String deviceName = _currentDeviceName ?? "";
Logger.i('BleManager',
'创建新的设备信息对象: address=$deviceAddress, name=$deviceName');
deviceInfo.value = DeviceInfo(
address: deviceAddress,
name: deviceName,
batteryLevel: batteryLevel ?? 0,
leftEarBattery: leftEarBattery ?? 0,
rightEarBattery: rightEarBattery ?? 0,
caseBattery: caseBattery ?? 0,
leftEarCharging: leftEarCharging ?? false,
rightEarCharging: rightEarCharging ?? false,
caseCharging: caseCharging ?? false,
firmwareVersion: firmwareVersion ?? '',
firmwareProductId: firmwareProductId ?? '',
firmwareProductColor: firmwareProductColor ?? '',
wakeupLeftVersion: wakeupLeftVersion ?? '',
wakeupRightVersion: wakeupRightVersion ?? '',
);
}
}
// 新增智能电池值选择方法
int? _getValidBatteryValue(int? newValue, int? currentValue) {
// 如果新值为null,使用当前值
if (newValue == null) return currentValue;
// 如果当前值为null或0,使用新值(即使新值为0)
if (currentValue == null || currentValue == 0) return newValue;
// 如果新值为0但当前值不为0,保持当前值(避免有效数据被0覆盖)
if (newValue == 0 && currentValue > 0) {
Logger.warning('检测到电池数据异常(新值为0),保持当前值: $currentValue');
return currentValue;
}
// 其他情况使用新值
return newValue;
}
/// 清除设备信息(用于完全断开连接或切换设备时)
void clearDeviceInfo() {
Logger.info('清除设备信息');
deviceInfo.value = null;
currentDeviceAddress = null;
_currentDeviceName = null;
}
/// 连接成功后获取设备信息
Future<void> fetchDeviceInfo() async {
Logger.info('开始获取BLE设备详细信息');
try {
// 确保设备信息对象存在
if (deviceInfo.value == null && _isConnected.value) {
// 创建初始设备信息对象
String deviceAddress = currentDeviceAddress ?? "";
String deviceName = _currentDeviceName ?? "";
Logger.info('初始化设备信息对象: address=$deviceAddress, name=$deviceName');
deviceInfo.value = DeviceInfo(
address: deviceAddress,
name: deviceName,
batteryLevel: 0,
leftEarBattery: 0,
rightEarBattery: 0,
caseBattery: 0,
leftEarCharging: false,
rightEarCharging: false,
caseCharging: false,
firmwareVersion: '',
firmwareProductId: '',
firmwareProductColor: '',
wakeupLeftVersion: '',
wakeupRightVersion: '',
);
}
// 并行获取所有设备信息,不使用延迟
final results = await Future.wait([
_bleService.getBatteryInfo(),
_bleService.getDeviceVersion(),
_bleService.getProductId(),
_bleService.getVoiceWakeupStatus(),
]);
Logger.info(
'设备信息获取结果: 电池=${results[0]}, 版本=${results[1]}, 产品ID=${results[2]}, 唤醒状态=${results[3]}');
if (!results[0]) {
Logger.warning('电池信息获取指令发送失败');
}
if (!results[1]) {
Logger.warning('版本信息获取指令发送失败');
}
if (!results[2]) {
Logger.warning('产品ID获取指令发送失败');
}
if (!results[3]) {
Logger.warning('语音唤醒状态获取指令发送失败');
_voiceWakeupSupported.value = false;
}
} catch (e) {
Logger.error('获取BLE设备信息失败: ${e.toString()}');
}
}
/// 检查BLE连接状态
Future<int> getConnectionState() async {
try {
final isConnected = await _bleService.isConnected();
_connectionState.value = isConnected
? BleConnectionState.connected
: BleConnectionState.disconnected;
_isConnected.value = isConnected;
return _connectionState.value;
} catch (e) {
Logger.error('获取BLE连接状态失败: ${e.toString()}');
return BleConnectionState.error;
}
}
String getDeviceName() {
if (deviceInfo.value != null) {
return deviceInfo.value!.name;
}
return "";
}
// 加载TTS偏好设置
Future<bool> _loadTtsInterrupe() async {
try {
if (!_storage.hasData("agent_tts_interrupt")) {
// 获取设备类型以确定检查策略
String deviceName = getDeviceName();
DBProduct? product = AppConfig.getproduct(deviceName);
if (deviceName != "" && product != null) {
// Logger.d('AgentController',
// 'liwe----------product $deviceName ${product.toJson()}');
return !product.iscontinuouschat!;
} else {
return false;
}
} else {
return _storage.read('agent_tts_interrupt') ?? false;
}
} catch (e) {
return false;
}
}
/// 启动BLE服务
Future<bool> startAgentService() async {
try {
// 从.env文件直接读取环境变量
var serverurl = DioManager().baseUrl;
var jsonString = _storage.read<String>("logintoken");
final azureSpeechKey = AppConfig.env('AZURE_SPEECH_KEY') ?? '';
final azureSpeechRegion =
AppConfig.env('AZURE_SPEECH_REGION') ?? 'eastasia';
final openaiApiKey = AppConfig.env('OPENAI_API_KEY') ?? '';
final openaiBaseUrl =
AppConfig.env('OPENAI_BASE_URL') ?? 'https://api.openai.com/v1';
final openaiModel = AppConfig.env('OPENAI_MODEL') ?? 'gpt-3.5-turbo';
final openaiModelMedia = AppConfig.env('OPENAI_API_VISION_MODEL') ??
'doubao-1-5-vision-pro-32k-250115';
final openaiagentid =
AppConfig.env('OPENAI_AGENT_ID') ?? 'personal_assistant';
final systemPrompt = AppConfig.agentsystemPrompt(openaiagentid);
var musicServiceType =
AppConfig.env('APP_ANDROID_MUSICSERVICE_TYPE') ?? 'AUTO';
if (Platform.isIOS) {
musicServiceType = AppConfig.env('APP_IOS_MUSICSERVICE_TYPE') ?? 'AUTO';
}
final navigationMode =
AppConfig.env('APP_NAVIGATION_MODE') ?? 'external_auto';
final xunfeiAppId = AppConfig.env('XUNFEI_ASR_APP_ID') ?? '';
final xunfeiAccessKeyId = AppConfig.env('XUNFEI_ASR_ACCESS_KEY_ID') ?? '';
final xunfeiAccessKeySecret =
AppConfig.env('XUNFEI_ASR_ACCESS_KEY_SECRET') ?? '';
// 从Flutter资源包中加载.mcp.json
final jsonData = AppConfig.mcpConfig();
Logger.info('启动BLE服务,musicServiceType: $musicServiceType');
final isTtsInterruptEnabled = await _loadTtsInterrupe();
final vipExpiryTime = userInfo.User.instance.vipexptime;
Logger.info('liwei------ vipexptime 1: $vipExpiryTime');
// 根据APP语言动态配置语音识别语言
List<String> supportedLanguages = [];
try {
final storage = GetStorage();
final languageManager = Get.find<LanguageManager>();
// 从存储中读取用户设置的语言
String? savedLanguage = storage.read('language');
if (savedLanguage != null && savedLanguage != 'system') {
// 用户设置了特定语言
final parts = savedLanguage.split('_');
if (parts.length == 2) {
final lang = parts[0].toLowerCase();
final country = parts[1].toUpperCase();
if (lang == 'zh' &&
(country == 'HK' || country == 'TW' || country == 'CN')) {
supportedLanguages.add('zh-$country');
} else if (lang == 'tl' && country == 'PH') {
// 菲律宾语特殊处理,映射为azure语言码
supportedLanguages.add('fil-PH');
} else {
supportedLanguages.add('$lang-$country');
}
}
} else {
// 跟随系统语言或未设置
final appLocale = Get.locale ?? AppTranslations.deviceLocale();
if (appLocale != null) {
final lang = appLocale.languageCode.toLowerCase();
final country = appLocale.countryCode;
if (lang.toLowerCase() == 'zh' &&
country != null &&
country.isNotEmpty &&
(country.toUpperCase() == 'HK' ||
country.toUpperCase() == 'TW' ||
country.toUpperCase() == 'CN')) {
supportedLanguages.add('zh-${country.toUpperCase()}');
} else {
if (country != null && country.isNotEmpty) {
final normalizedCountry = country.toUpperCase();
if (lang == 'tl' && normalizedCountry == 'PH') {
supportedLanguages.add('fil-PH');
} else {
supportedLanguages.add('$lang-$normalizedCountry');
}
} else {
final asrCode = languageManager.getAsrCodeByShortCode(lang);
if (asrCode != null) {
supportedLanguages.add(asrCode);
} else {
supportedLanguages.add('en-US');
}
}
}
}
}
// 最终兜底
if (supportedLanguages.isEmpty) {
supportedLanguages.add('en-US');
}
// Logger.info('动态配置语音识别语言: ${supportedLanguages.join(', ')}');
} catch (e) {
Logger.error('获取APP语言配置失败,使用默认语言: $e');
supportedLanguages = ['zh-CN', 'en-US'];
}
// STS 模式参数(移动精灵)
// 仅以配对时根据 DBProduct.channel=='mobile' 写入的 storage 为准,
// 不做任何 env 兜底——若 storage 里无对应参数,进入移动精灵时直接报错暴露问题
final agentType = _storage.read<String>('device_agent_type') ?? '';
final agentMode = (agentType == 'yidong') ? 'sts' : 'stt_llm_tts';
final agentId = (agentType == 'yidong') ? 'mobile_elf' : 'default_agent';
// STS 平台认证参数从 DBProduct.channelextra 解析后存入 storage,
// 不再读取 .env 中的 MOBILE_ELF_PRODUCT_ID/KEY
final stsProductId = _storage.read<String>('sts_product_id') ?? '';
final stsProductKey = _storage.read<String>('sts_product_key') ?? '';
// 使用当前连接设备的 SN(配对时写入),认证失败由原生层上报 credential_failed 事件
final stsDeviceNo = _storage.read<String>('device_sts_code') ?? '';
final stsDeviceId = _storage.read<String>('sts_device_id') ?? '';
final stsDeviceSecret = _storage.read<String>('sts_device_secret') ?? '';
// 从 UserDevice 获取当前连接设备的 CMEI
final mac = currentDeviceAddress?.toUpperCase() ?? '';
String stsCmei = '';
try {
final matchedDevice = userInfo.User.instance.devices.firstWhere(
(d) => d.devicemac.toUpperCase() == mac,
);
stsCmei = matchedDevice.cmei;
} catch (_) {}
Logger.i('BleManager', '当前设备 CMEI: $stsCmei (MAC=$mac)');
final config = <String, dynamic>{
// STS 模式路由
'agentMode': agentMode,
'agentId': agentId,
'stsProductId': stsProductId,
'stsProductKey': stsProductKey,
'stsDeviceNo': stsDeviceNo,
'stsDeviceId': stsDeviceId,
'stsDeviceSecret': stsDeviceSecret,
'stsCmei': stsCmei,
'stsMac': mac,
// 语音代理配置参数
'azureSpeechKey': azureSpeechKey,
'azureSpeechRegion': azureSpeechRegion,
'xunfeiAppId': xunfeiAppId,
'xunfeiAccessKeyId': xunfeiAccessKeyId,
'xunfeiAccessKeySecret': xunfeiAccessKeySecret,
'openaiApiKey': openaiApiKey,
'openaiBaseUrl': openaiBaseUrl,
'openaiModel': openaiModel,
'openaivisionModel': openaiModelMedia,
'systemPrompt': systemPrompt,
'mcpServer': jsonEncode(jsonData),
'volcanoAppId': '2316081933',
'volcanoToken': 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En',
'serverurl': serverurl,
'usertoken': jsonString,
'supportedLanguages': supportedLanguages,
'musicServiceType': musicServiceType,
"navigationMode": navigationMode,
"isttsinterrupt": isTtsInterruptEnabled ? "true" : "false",
"vipexptime": vipExpiryTime, //会员过期时间
"callingToolText": "callingTool".tr,
"insufficientIntegralText": "insufficientIntegral".tr,
};
Logger.info('启动BLE服务,配置参数键名: ${config.keys.toList()}');
if (xunfeiAppId.isEmpty ||
xunfeiAccessKeyId.isEmpty ||
xunfeiAccessKeySecret.isEmpty) {
Logger.warning('XUNFEI_ASR_* 未完整配置,ASR 将不会切到讯飞');
}
if (agentMode != 'sts' && (azureSpeechKey.isEmpty || openaiApiKey.isEmpty)) {
Logger.warning('缺少必要的API密钥,请检查.env文件配置');
// 检查.env中相关密钥是否存在
if (azureSpeechKey.isEmpty) {
Logger.warning('AZURE_SPEECH_KEY未配置在.env文件中');
}
if (openaiApiKey.isEmpty) {
Logger.warning('OPENAI_API_KEY未配置在.env文件中');
}
}
// 设置原生日志回调,将 Agent 底层日志写入文件
AgentService.nativeLogCallback ??= (level, message) {
switch (level) {
case 'ERROR':
Logger.e('NativeAgent', message);
break;
case 'WARNING':
Logger.w('NativeAgent', message);
break;
case 'DEBUG':
Logger.d('NativeAgent', message);
break;
default:
Logger.i('NativeAgent', message);
}
};
// STS 模式:监听凭证获取事件并缓存到 GetStorage
if (agentMode == 'sts') {
AgentService.events.where((e) => e.type == AgentServiceEventType.stsCredentials).listen((e) {
final deviceId = e.data['deviceId']?.toString() ?? '';
final deviceSecret = e.data['deviceSecret']?.toString() ?? '';
final deviceNo = e.data['deviceNo']?.toString() ?? '';
if (deviceId.isNotEmpty) _storage.write('sts_device_id', deviceId);
if (deviceSecret.isNotEmpty) _storage.write('sts_device_secret', deviceSecret);
if (deviceNo.isNotEmpty) _storage.write('sts_device_no', deviceNo);
Logger.info('STS 凭证已缓存: deviceId=$deviceId deviceNo=$deviceNo');
});
}
final result = await AgentService.startAgentService(config);
Logger.i('BleManager', 'AgentService.startAgentService 结果: $result');
if (result) {
final isConnected = await _bleService.isConnected();
if (isConnected) {
_isConnected.value = true;
_connectionState.value = BleConnectionState.connected;
}
} else {
Logger.e('BleManager', 'BLE服务启动失败');
}
return result;
} catch (e) {
Logger.error('启动BLE服务失败: ${e.toString()}');
return false;
}
}
/// 停止BLE服务
Future<bool> stopAgentService() async {
try {
final result = await AgentService.stopAgentService();
if (result) {
Logger.info('BLE服务停止成功');
_isConnected.value = false;
_connectionState.value = BleConnectionState.disconnected;
// 停止服务时清除设备信息
clearDeviceInfo();
} else {
Logger.error('BLE服务停止失败');
}
return result;
} catch (e) {
Logger.error('停止BLE服务失败: ${e.toString()}');
return false;
}
}
/// 获取电池电量
Future<int> getBatteryLevel() async {
try {
return await _bleService.getBatteryLevel();
} catch (e) {
Logger.error('获取电池电量失败: ${e.toString()}');
return 0;
}
}
/// 主动获取电池信息
Future<bool> fetchBatteryInfo() async {
try {
//Logger.info('lxm--fetchBatteryInfo被调用,准备向设备发送获取电池信息指令');
final result = await _bleService.getBatteryInfo();
//Logger.info('lxm--getBatteryInfo指令发送结果: $result');
return result;
} catch (e) {
//Logger.error('lxm--获取电池信息失败: ${e.toString()}');
return false;
}
}
/// 主动产品id
Future<bool> getProductId() async {
try {
return await _bleService.getProductId();
} catch (e) {
Logger.error('获取设备版本失败: ${e.toString()}');
return false;
}
}
/// 主动获取设备版本信息
Future<bool> fetchDeviceVersion() async {
try {
return await _bleService.getDeviceVersion();
} catch (e) {
Logger.error('获取设备版本失败: ${e.toString()}');
return false;
}
}
/// 打开编码器
Future<bool> openEncoder() async {
try {
return await _bleService.openEncoder();
} catch (e) {
Logger.error('打开编码器失败: ${e.toString()}');
return false;
}
}
/// 打开解码器
Future<bool> openDecoder() async {
try {
_awaitingCodecOpen = true;
_awaitingCodecClose = false;
_isCodecActive.value = false;
return await _bleService.openDecoder();
} catch (e) {
Logger.error('打开解码器失败: ${e.toString()}');
_awaitingCodecOpen = false;
return false;
}
}
/// 打开A2DP解码器
Future<bool> openA2DPDecoder() async {
try {
_awaitingCodecOpen = true;
_awaitingCodecClose = false;
_isCodecActive.value = false;
Logger.i('CALL_TRANS_CMD',
'准备发送通话翻译指令 cmd=0x05 sub=0xA2(A2DP_PLAY) → openA2DPDecoder');
final ok = await _bleService.openA2DPDecoder();
Logger.i('CALL_TRANS_CMD',
'通话翻译指令下发结果: ${ok ? "成功" : "失败"} (等待设备回包确认)');
return ok;
} catch (e) {
Logger.e('CALL_TRANS_CMD', '通话翻译指令异常: ${e.toString()}');
_awaitingCodecOpen = false;
return false;
}
}
/// 打开解码器
Future<bool> openCallRecordDecoder() async {
try {
return await _bleService.openCallRecordDecoder();
} catch (e) {
Logger.error('打开解码器失败: ${e.toString()}');
return false;
}
}
/// 关闭编解码器
Future<bool> closeCodec() async {
try {
_awaitingCodecOpen = false;
_awaitingCodecClose = true;
_isCodecActive.value = false;
return await _bleService.closeCodec();
} catch (e) {
Logger.error('关闭编解码器失败: ${e.toString()}');
_awaitingCodecClose = false;
return false;
}
}
/// 播放A2DP
Future<bool> playA2DP() async {
try {
return await _bleService.playA2DP();
} catch (e) {
Logger.error('播放A2DP失败: ${e.toString()}');
return false;
}
}
// ==================== 配对相关方法 ====================
/// 检查设备是否支持Companion Device Manager
Future<bool> isCompanionSupported() async {
try {
return await _bleService.isCompanionSupported();
} catch (e) {
Logger.error('检查Companion支持失败: ${e.toString()}');
return false;
}
}
/// 获取已配对的MAC地址
Future<String?> getPairedMacAddress() async {
try {
return await _bleService.getPairedMacAddress();
} catch (e) {
Logger.error('获取已配对MAC地址失败: ${e.toString()}');
return null;
}
}
/// 检查BLE连接状态
Future<bool> isBleConnected() async {
try {
return await _bleService.isBleConnected();
} catch (e) {
Logger.error('检查BLE连接状态失败: ${e.toString()}');
return false;
}
}
/// 设置是否是ota模式
Future<void> setEnterOta(bool enterOta) async {
try {
await _bleService.setEnterOta(enterOta);
} catch (e) {
Logger.error('设置是否是ota模式失败: ${e.toString()}');
}
}
/// 断开连接
Future<void> disconnect() async {
try {
await _bleService.disconnect();
} catch (e) {
Logger.error('断开连接失败: ${e.toString()}');
}
}
/// 验证设备是否可以配对
Future<void> startScan(List<String> uuids) async {
try {
await _bleService.startScan(uuids);
} catch (e) {
Logger.error('开始扫描设备: ${e.toString()}');
}
}
/// 验证设备是否可以配对
Future<void> stopScan() async {
try {
await _bleService.stopScan();
} catch (e) {
Logger.error('停止扫描设备: ${e.toString()}');
}
}
Future<Map<String, String>?> getDeviceInfo(String deviceName) async {
try {
final result = _bleService.getDeviceInfo(deviceName);
return result;
} catch (e) {
Logger.error('获取设备信息: ${e.toString()}');
return null;
}
}
Future<List<Map<String, String>>?> getDeviceList(String deviceName) async {
try {
final result = _bleService.getDeviceList(deviceName);
return result;
} catch (e) {
Logger.error('获取设备信息: ${e.toString()}');
return null;
}
}
/// 验证设备是否可以配对
Future<bool> verifyDeviceCanPair(String macAddress) async {
try {
return true;
} catch (e) {
Logger.error('验证设备配对失败: ${e.toString()}');
return false;
}
}
/// 启动设备关联
Future<bool> startDeviceAssociation({
required String deviceNamePattern,
required String macAddress,
}) async {
try {
return await _bleService.startDeviceAssociation(
deviceNamePattern: deviceNamePattern,
macAddress: macAddress,
);
} catch (e) {
Logger.error('启动设备关联失败: ${e.toString()}');
return false;
}
}
/// 启动设备关联
Future<void> connect() async {
try {
await _bleService.connect();
} catch (e) {
Logger.error('连接设备失败: ${e.toString()}');
}
}
/// 注册后台扫描
Future<bool> registerBackgroundScan({
String? macAddress,
List<String>? uuids,
}) async {
try {
// 在注册新设备前先清除旧设备信息
if (macAddress != null && macAddress != currentDeviceAddress) {
clearDeviceInfo(); // 清除上一个设备的信息
}
// 保存设备地址信息
if (macAddress != null) {
currentDeviceAddress = macAddress;
// 从蓝牙管理器获取设备名称
final bluetoothManager = Get.find<BluetoothManager>();
_currentDeviceName = bluetoothManager.currentDevice?.name ?? "";
Logger.info(
'保存设备信息: address=$currentDeviceAddress, name=$_currentDeviceName');
}
return await _bleService.registerBackgroundScan(
macAddress: macAddress,
uuids: uuids,
);
} catch (e) {
Logger.error('注册后台扫描失败: ${e.toString()}');
return false;
}
}
/// 注销后台扫描
Future<bool> unregisterBackgroundScan() async {
try {
return await _bleService.unregisterBackgroundScan();
} catch (e) {
Logger.error('注销后台扫描失败: ${e.toString()}');
return false;
}
}
/// 清除BLE配对设备
Future<bool> clearPairedDevices() async {
try {
return await _bleService.clearAssociations();
} catch (e) {
Logger.error('清除BLE配对设备失败: ${e.toString()}');
return false;
}
}
/// 注册服务
static Future<BleManager> init() async {
if (!Get.isRegistered<BleManager>()) {
Get.put(BleManager(), permanent: true);
}
return Future.value(Get.find<BleManager>());
}
/// 请求蓝牙权限
Future<bool> requestBluetoothPermission() async {
try {
return await _bleService.requestBluetoothPermission();
} catch (e) {
Logger.error('请求蓝牙权限失败: ${e.toString()}');
return false;
}
}
/// 开始唤醒词升级
Future<bool> startWakeupVoicesUpgrade(List<int> wakeupVoiceBytes) async {
try {
return await _bleService.startWakeupVoicesUpgrade(wakeupVoiceBytes);
} catch (e) {
Logger.error('开始唤醒词升级失败: ${e.toString()}');
return false;
}
}
}