Browse Source

上传代码

newdev_shunjiawei
liwei1dao 10 months ago
parent
commit
678e6ae1eb
  1. 58
      lib/data/services/ble_manager.dart
  2. 185
      lib/modules/wakeupvoice/controllers/wakeup_voice_controller.dart
  3. 120
      lib/modules/wakeupvoice/views/wakeup_voice_view.dart
  4. 25
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt
  5. 4
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt
  6. 24
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  7. 25
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleWakeupVoicesUpgradeSender.kt

58
lib/data/services/ble_manager.dart

@ -24,6 +24,7 @@ class BleCommands {
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;
}
/// 蓝牙设备信息缓存模型
@ -49,6 +50,10 @@ class DeviceInfo {
final String firmwareProductColor;
final DateTime lastUpdated;
//唤醒词版本
final String wakeupLeftVersion;
final String wakeupRightVersion;
DeviceInfo({
required this.address,
required this.name,
@ -63,6 +68,8 @@ class DeviceInfo {
this.firmwareProductId = '',
this.firmwareProductColor = '',
DateTime? lastUpdated,
this.wakeupLeftVersion = '',
this.wakeupRightVersion = '',
}) : lastUpdated = lastUpdated ?? DateTime.now();
/// 创建更新后的设备信息副本
@ -79,6 +86,8 @@ class DeviceInfo {
String? firmwareVersion,
String? firmwareProductId,
String? firmwareProductColor,
String? wakeupLeftVersion,
String? wakeupRightVersion,
}) {
return DeviceInfo(
address: address ?? this.address,
@ -94,6 +103,8 @@ class DeviceInfo {
firmwareProductId: firmwareProductId ?? this.firmwareProductId,
firmwareProductColor: firmwareProductColor ?? this.firmwareProductColor,
lastUpdated: DateTime.now(),
wakeupLeftVersion: wakeupLeftVersion ?? this.wakeupLeftVersion,
wakeupRightVersion: wakeupRightVersion ?? this.wakeupRightVersion,
);
}
@ -102,7 +113,9 @@ class DeviceInfo {
return 'DeviceInfo(address: $address, name: $name, '
'battery: $batteryLevel%, L: $leftEarBattery%, R: $rightEarBattery%, Case: $caseBattery%, '
'charging: L:$leftEarCharging, R:$rightEarCharging, C:$caseCharging, '
'version: $firmwareVersion)';
'version: $firmwareVersion, '
'wakeupLeftVersion: $wakeupLeftVersion, '
'wakeupRightVersion: $wakeupRightVersion)';
}
}
@ -218,6 +231,8 @@ class BleManager extends GetxService {
firmwareVersion: '',
firmwareProductId: '',
firmwareProductColor: '',
wakeupLeftVersion: '',
wakeupRightVersion: '',
);
// 断开连接时不清除设备信息,保留最后的电量信息
// 只有在需要完全清除设备时才重置信息
@ -337,6 +352,10 @@ class BleManager extends GetxService {
print(" CONTROL_CODEC: $commandId,$data");
//_isCall.value = data['callStatus'] as bool? ?? false;
break;
case BleCommands.GET_WAKEUP_VERSION:
Logger.info('lxm--处理唤醒语音版本命令');
_updateWakeupVersionFromData(data);
break;
default:
Logger.warning('lxm--未处理的命令ID: $commandId,数据: $data');
print(" 未处理的命令ID: $commandId,$data");
@ -499,6 +518,33 @@ class BleManager extends GetxService {
}
}
/// 更新唤醒语音版本信息
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()}');
}
}
/// 更新设备信息(如果已存在设备)
void _updateDeviceInfoIfNeeded({
int? batteryLevel,
@ -511,6 +557,8 @@ class BleManager extends GetxService {
String? firmwareVersion,
String? firmwareProductId,
String? firmwareProductColor,
String? wakeupLeftVersion,
String? wakeupRightVersion,
}) {
Logger.info('lxm--_updateDeviceInfoIfNeeded被调用');
Logger.info(
@ -540,6 +588,10 @@ class BleManager extends GetxService {
firmwareProductId ?? deviceInfo.value!.firmwareProductId,
firmwareProductColor:
firmwareProductColor ?? deviceInfo.value!.firmwareProductColor,
wakeupLeftVersion:
wakeupLeftVersion ?? deviceInfo.value!.wakeupLeftVersion,
wakeupRightVersion:
wakeupRightVersion ?? deviceInfo.value!.wakeupRightVersion,
);
Logger.info('lxm--更新后的设备信息: ${deviceInfo.value}');
@ -568,6 +620,8 @@ class BleManager extends GetxService {
firmwareVersion: firmwareVersion ?? '',
firmwareProductId: firmwareProductId ?? '',
firmwareProductColor: firmwareProductColor ?? '',
wakeupLeftVersion: wakeupLeftVersion ?? '',
wakeupRightVersion: wakeupRightVersion ?? '',
);
Logger.info('lxm--新创建的设备信息: ${deviceInfo.value}');
@ -628,6 +682,8 @@ class BleManager extends GetxService {
firmwareVersion: '',
firmwareProductId: '',
firmwareProductColor: '',
wakeupLeftVersion: '',
wakeupRightVersion: '',
);
}

185
lib/modules/wakeupvoice/controllers/wakeup_voice_controller.dart

@ -28,6 +28,9 @@ class WakeupVoiceController extends GetxController {
// 设备状态(用于页面头部展示)
final RxString deviceName = ''.obs;
final RxBool isConnected = false.obs;
final RxBool wakeupVersionMismatch = false.obs;
final RxnInt deviceWakeIdCode = RxnInt();
final RxnInt deviceWakeVerCode = RxnInt();
// 预览/测试模式:跳过 onReady 自动拉取
bool skipAutoFetch = false;
@ -54,6 +57,10 @@ class WakeupVoiceController extends GetxController {
}
}
/// 绑定 BLE 相关的观察者
/// 参数:无
/// 返回:无
/// 异常:发生监听绑定异常时捕获并记录日志,不抛出
void _bindBleObservers() {
// 同步连接状态与设备名
try {
@ -61,6 +68,7 @@ class WakeupVoiceController extends GetxController {
final info = bleManager.deviceInfo.value;
if (info != null && info.name.isNotEmpty) {
deviceName.value = info.name;
_syncDeviceWakeVersions(info);
}
// 监听连接状态变化
ever<int>(bleManager.connectionStateRx, (state) {
@ -68,12 +76,14 @@ class WakeupVoiceController extends GetxController {
final info = bleManager.deviceInfo.value;
if (info != null && info.name.isNotEmpty) {
deviceName.value = info.name;
_syncDeviceWakeVersions(info);
}
});
// 监听设备信息更新
ever<DeviceInfo?>(bleManager.deviceInfo, (info) {
if (info != null && info.name.isNotEmpty) {
deviceName.value = info.name;
_syncDeviceWakeVersions(info);
}
});
// 监听升级进度
@ -107,6 +117,10 @@ class WakeupVoiceController extends GetxController {
}
// 供外部设置当前设备语音包信息
/// 设置当前设备的唤醒语音包信息
/// 参数:`id` 唤醒词ID;`version` 版本字符串(如 `1.2.3.4`)
/// 返回:无
/// 异常:无
void setCurrentDeviceWakeVoice({required int id, required String version}) {
currentDeviceVoiceId.value = id;
currentDeviceVoiceVersion.value = version;
@ -116,6 +130,10 @@ class WakeupVoiceController extends GetxController {
}
}
/// 拉取服务端唤醒语音包列表
/// 参数:无
/// 返回:`Future<void>`,完成后 `voices`、`selectedVoiceId` 等被更新
/// 异常:网络或数据解析异常会写入 `loadError` 并弹出提示,不抛出
Future<void> fetchVoices() async {
try {
isLoading.value = true;
@ -134,13 +152,25 @@ class WakeupVoiceController extends GetxController {
.map((e) => WakeupVoiceItem.fromJson(e as Map<String, dynamic>))
.toList());
// 默认选择:优先当前设备项,否则第一个
if (currentDeviceVoiceId.value != null &&
voices.any((v) => v.id == currentDeviceVoiceId.value)) {
selectedVoiceId.value = currentDeviceVoiceId.value;
} else if (voices.isNotEmpty) {
if (voices.isNotEmpty) {
selectedVoiceId.value = voices.first.id;
}
final devIdCode = deviceWakeIdCode.value;
if (devIdCode != null) {
final sameIdItems = voices.where((v) {
final p = _parseVersion(v.version);
return p != null &&
p.length >= 2 &&
((p[0] << 8) | p[1]) == devIdCode;
}).toList();
if (sameIdItems.isNotEmpty) {
selectedVoiceId.value = sameIdItems.first.id;
currentDeviceVoiceId.value = devIdCode;
}
}
if (wakeupVersionMismatch.value) {
loadError.value = '设备异常:左右唤醒词版本不一致';
}
} catch (e) {
loadError.value = '加载唤醒语音失败: $e';
Get.snackbar('错误', loadError.value);
@ -149,43 +179,138 @@ class WakeupVoiceController extends GetxController {
}
}
/// 解析版本字符串为四段整型数组
/// 参数:`v` 版本字符串,期望形如 `A.B.C.D`(可包含非数字分隔符)
/// 返回:`List<int>?` 长度为4的数组,若解析失败返回 `null`
/// 异常:无
List<int>? _parseVersion(String v) {
final parts =
v.split(RegExp(r'[^0-9]+')).where((e) => e.isNotEmpty).toList();
if (parts.length < 4) return null;
final nums = parts.take(4).map((e) => int.tryParse(e) ?? 0).toList();
return nums;
}
/// 同步设备左右耳唤醒词版本信息,并计算设备唤醒词ID与版本编码
/// 参数:`info` 设备信息对象,需包含 `wakeupLeftVersion` 与 `wakeupRightVersion`
/// 返回:无
/// 异常:无;若版本缺失或解析失败则静默返回
void _syncDeviceWakeVersions(DeviceInfo info) {
final left = info.wakeupLeftVersion;
final right = info.wakeupRightVersion;
wakeupVersionMismatch.value =
left.isNotEmpty && right.isNotEmpty && left != right;
final base = left.isNotEmpty ? left : right;
if (base.isEmpty) return;
final p = _parseVersion(base);
if (p == null || p.length < 4) return;
deviceWakeIdCode.value = (p[0] << 8) | p[1];
deviceWakeVerCode.value = (p[2] << 8) | p[3];
currentDeviceVoiceId.value = deviceWakeIdCode.value;
currentDeviceVoiceVersion.value = '${p[2]}.${p[3]}';
}
/// 判断候选项与设备的唤醒词ID是否一致
/// 参数:`item` 唤醒词项,若为空返回 `false`
/// 返回:`bool`,一致返回 `true`
/// 异常:无
bool _isSameWakeId(WakeupVoiceItem? item) {
if (item == null) return false;
final p = _parseVersion(item.version);
if (p == null || p.length < 2) return false;
final idCode = (p[0] << 8) | p[1];
return deviceWakeIdCode.value != null && deviceWakeIdCode.value == idCode;
}
/// 判断候选项与设备的唤醒词版本是否一致
/// 参数:`item` 唤醒词项,若为空返回 `false`
/// 返回:`bool`,一致返回 `true`
/// 异常:无
bool _isSameWakeVersion(WakeupVoiceItem? item) {
if (item == null) return false;
final p = _parseVersion(item.version);
if (p == null || p.length < 4) return false;
final verCode = (p[2] << 8) | p[3];
return deviceWakeVerCode.value != null &&
deviceWakeVerCode.value == verCode;
}
// 选择语音包
/// 选择语音包
/// 参数:`id` 待选择的语音包ID
/// 返回:无
/// 异常:无
void selectVoice(int id) {
selectedVoiceId.value = id;
}
// 动作按钮文案与状态
/// 获取当前选择项对应的操作文案(基于设备与候选项版本关系)
/// 参数:无(使用 `selectedVoiceId`)
/// 返回:`String`,可能为 `设备异常`、`升级`、`切换`、`已是最新`、`处理`
/// 异常:无
String get actionText {
final sel = selectedVoiceId.value;
final curId = currentDeviceVoiceId.value;
final curVer = currentDeviceVoiceVersion.value;
final selected = _findById(sel);
if (sel == null || selected == null) return '处理';
if (wakeupVersionMismatch.value) return '设备异常';
if (curId == sel) {
if (curVer != null && curVer != selected.version) {
return '升级';
}
return '已是最新';
} else {
return '切换';
if (_isSameWakeId(selected)) {
return _isSameWakeVersion(selected) ? '已是最新' : '升级';
}
return '切换';
}
/// 获取当前选择项是否允许触发操作
/// 参数:无(使用 `selectedVoiceId`)
/// 返回:`bool`,当 `设备异常` 或 `已是最新` 时为 `false`
/// 异常:无
bool get actionEnabled {
final sel = selectedVoiceId.value;
final curId = currentDeviceVoiceId.value;
final curVer = currentDeviceVoiceVersion.value;
final selected = _findById(sel);
if (sel == null || selected == null) return false;
if (!isConnected.value) return false;
if (wakeupVersionMismatch.value) return false;
if (_isSameWakeId(selected)) {
return !_isSameWakeVersion(selected);
}
return true;
}
if (curId == sel) {
return curVer != null && curVer != selected.version; // 版本不一致才可升级
} else {
return true; // 选择其他可切换
/// 计算指定语音项的操作文案(不影响当前选择项)
/// 参数:`item` 候选语音项
/// 返回:`String`,可能为 `设备异常`、`升级`、`切换`、`已是最新`
/// 异常:无
String actionTextForItem(WakeupVoiceItem item) {
if (wakeupVersionMismatch.value) return '设备异常';
if (_isSameWakeId(item)) {
return _isSameWakeVersion(item) ? '已是最新' : '升级';
}
return '切换';
}
/// 计算指定语音项是否允许操作(不影响当前选择项)
/// 参数:`item` 候选语音项
/// 返回:`bool`,当 `设备异常` 或 `已是最新` 时为 `false`
/// 异常:无
bool actionEnabledForItem(WakeupVoiceItem item) {
if (!isConnected.value) return false;
if (wakeupVersionMismatch.value) return false;
if (_isSameWakeId(item)) {
return !_isSameWakeVersion(item);
}
return true;
}
/// 根据当前选择项触发操作
/// 参数:无
/// 返回:`Future<void>`
/// 异常:无;当不允许操作时直接返回
Future<void> onActionPressed() async {
final label = actionText;
if (!actionEnabled) return;
@ -202,12 +327,20 @@ class WakeupVoiceController extends GetxController {
}
}
/// 将当前设备升级到已选择项
/// 参数:无(使用 `selectedVoiceId`)
/// 返回:`Future<void>`
/// 异常:下载或升级失败会通过提示与日志反馈,不抛出
Future<void> upgradeWakeVoice() async {
final sel = _findById(selectedVoiceId.value);
if (sel == null) return;
await _startUpgradeByItem(sel);
}
/// 将当前设备切换到已选择项
/// 参数:无(使用 `selectedVoiceId`)
/// 返回:`Future<void>`
/// 异常:下载或升级失败会通过提示与日志反馈,不抛出
Future<void> switchWakeVoice() async {
final sel = _findById(selectedVoiceId.value);
if (sel == null) return;
@ -215,12 +348,20 @@ class WakeupVoiceController extends GetxController {
}
// 直接切换到指定语音包(页面卡片按钮使用)
/// 直接切换到指定语音项
/// 参数:`item` 目标语音项
/// 返回:`Future<void>`
/// 异常:下载或升级失败会通过提示与日志反馈,不抛出
Future<void> switchWakeVoiceTo(WakeupVoiceItem item) async {
selectedVoiceId.value = item.id;
await _startUpgradeByItem(item);
}
// 直接升级到指定语音包(页面卡片按钮使用)
/// 直接升级到指定语音项
/// 参数:`item` 目标语音项
/// 返回:`Future<void>`
/// 异常:下载或升级失败会通过提示与日志反馈,不抛出
Future<void> upgradeWakeVoiceTo(WakeupVoiceItem item) async {
selectedVoiceId.value = item.id;
await _startUpgradeByItem(item);
@ -230,6 +371,10 @@ class WakeupVoiceController extends GetxController {
final RxnInt upgradingVoiceId = RxnInt();
final RxInt upgradeProgress = 0.obs;
/// 按指定语音项发起升级流程(下载并写入设备)
/// 参数:`item` 目标语音项
/// 返回:`Future<void>`
/// 异常:下载或BLE交互失败会通过提示与日志反馈,不抛出
Future<void> _startUpgradeByItem(WakeupVoiceItem item) async {
try {
if (item.packagaddress.isEmpty) {
@ -263,6 +408,10 @@ class WakeupVoiceController extends GetxController {
}
}
/// 下载语音包二进制数据
/// 参数:`url` 语音包地址
/// 返回:`Future<List<int>>` 字节列表;失败返回空列表
/// 异常:网络异常会记录日志并返回空列表
Future<List<int>> _downloadVoiceBytes(String url) async {
try {
final dio = Dio();
@ -280,6 +429,10 @@ class WakeupVoiceController extends GetxController {
}
}
/// 根据ID查找语音项
/// 参数:`id` 语音包ID
/// 返回:`WakeupVoiceItem?` 找到返回对象,否则 `null`
/// 异常:无
WakeupVoiceItem? _findById(int? id) {
if (id == null) return null;
for (final v in voices) {

120
lib/modules/wakeupvoice/views/wakeup_voice_view.dart

@ -54,11 +54,16 @@ class WakeupVoiceView extends GetView<WakeupVoiceController> {
}
// 顶部渐变头部
/// 构建顶部设备状态头部
/// 参数:`isDark` 是否深色模式
/// 返回:`Widget` 构建好的头部组件
/// 异常:无
Widget _buildHeader(bool isDark) {
final connected = controller.isConnected.value;
final deviceName = controller.deviceName.value.isEmpty
? '未连接设备'
: controller.deviceName.value;
final mismatch = controller.wakeupVersionMismatch.value;
return Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
@ -108,6 +113,23 @@ class WakeupVoiceView extends GetView<WakeupVoiceController> {
Text(connected ? '已连接' : '未连接',
style: const TextStyle(
color: Colors.white70, fontSize: 12)),
if (mismatch) ...[
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.2),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: Colors.redAccent),
),
child: const Text('设备异常',
style: TextStyle(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.w500)),
),
]
],
)
],
@ -121,6 +143,10 @@ class WakeupVoiceView extends GetView<WakeupVoiceController> {
}
// 当前唤醒词卡片
/// 构建当前设备唤醒词卡片
/// 参数:`isDark` 是否深色模式
/// 返回:`Widget` 构建好的卡片
/// 异常:无
Widget _buildCurrentWakeWordCard(bool isDark) {
final curId = controller.currentDeviceVoiceId.value;
final curVer = controller.currentDeviceVoiceVersion.value;
@ -146,17 +172,33 @@ class WakeupVoiceView extends GetView<WakeupVoiceController> {
],
),
const SizedBox(height: 8),
_buildVoiceCard(
isDark: isDark,
item: curItem,
isCurrent: true,
hasUpdate: _hasUpdateForCurrent(curItem),
),
if (curItem != null)
_buildVoiceCard(
isDark: isDark,
item: curItem,
isCurrent: true,
hasUpdate: curItem != null &&
controller.actionTextForItem(curItem) == '升级',
),
// 版本一致的唤醒词放在“当前唤醒词”下面
...controller.voices
.where((v) => controller.actionTextForItem(v) == '已是最新')
.where((v) => !identical(v, curItem))
.map((v) => _buildVoiceCard(
isDark: isDark,
item: v,
isCurrent: true,
hasUpdate: false,
)),
],
);
}
// 可用唤醒词列表
/// 构建可用唤醒词列表
/// 参数:`isDark` 是否深色模式
/// 返回:`Widget` 构建好的列表
/// 异常:无
Widget _buildAvailableWakeWords(bool isDark) {
final curId = controller.currentDeviceVoiceId.value;
return Column(
@ -168,24 +210,23 @@ class WakeupVoiceView extends GetView<WakeupVoiceController> {
if (controller.loadError.value.isNotEmpty)
Text(controller.loadError.value,
style: const TextStyle(color: Colors.red)),
...controller.voices.where((v) => v.id != curId).map(
(v) => _buildVoiceCard(isDark: isDark, item: v, isCurrent: false)),
// 如果存在更新项(同ID但版本更高),也在此展示
// 仅展示非“已是最新”的可用唤醒词(排除当前使用版本)
...controller.voices
.where((v) =>
v.id == curId &&
_versionHigherThan(
v.version, controller.currentDeviceVoiceVersion.value))
.where((v) => controller.actionTextForItem(v) != '已是最新')
.map((v) => _buildVoiceCard(
isDark: isDark,
item: v,
isCurrent: false,
hasUpdate: true,
hasUpdate: controller.actionTextForItem(v) == '升级',
)),
],
);
}
/// 比较版本是否更高(三段比较)
/// 参数:`a` 候选版本;`b` 当前版本(可空)
/// 返回:`bool` 若 `a` 高于 `b` 返回 `true`
/// 异常:无
bool _versionHigherThan(String a, String? b) {
if (b == null) return true;
// 简易版本比较:按点位分割逐段比较
@ -199,12 +240,20 @@ class WakeupVoiceView extends GetView<WakeupVoiceController> {
return false;
}
/// 判断当前卡片是否存在可升级版本
/// 参数:`curItem` 当前项(可空)
/// 返回:`bool` 存在升级返回 `true`
/// 异常:无
bool _hasUpdateForCurrent(WakeupVoiceItem? curItem) {
if (curItem == null) return false;
final curVer = controller.currentDeviceVoiceVersion.value;
return _versionHigherThan(curItem.version, curVer);
}
/// 构建通用语音卡片
/// 参数:`isDark` 是否深色模式;`item` 语音项;`isCurrent` 是否当前项;`hasUpdate` 是否升级卡片
/// 返回:`Widget` 卡片组件
/// 异常:无
Widget _buildVoiceCard({
required bool isDark,
required WakeupVoiceItem? item,
@ -212,28 +261,33 @@ class WakeupVoiceView extends GetView<WakeupVoiceController> {
bool hasUpdate = false,
}) {
if (item == null) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: const Text('未设置'),
);
if (isCurrent) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: const Text('未设置'),
);
} else {
return const SizedBox.shrink();
}
}
final accuracyText = controller.extractAccuracy(item) ?? '识别率 -';
final showCheck = isCurrent && !hasUpdate;
final actionColor =
hasUpdate ? const Color(0xFFFF7A00) : const Color(0xFF6A5AE0);
final actionText = hasUpdate ? '更新唤醒词' : '切换唤醒词';
final actionText = controller.actionTextForItem(item);
final actionEnabled = controller.actionEnabledForItem(item);
final isUpgrading =
item != null && controller.upgradingVoiceId.value == item.id;
final progress = controller.upgradeProgress.value;
@ -355,12 +409,12 @@ class WakeupVoiceView extends GetView<WakeupVoiceController> {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
onPressed: isUpgrading
onPressed: isUpgrading || !actionEnabled
? null
: () {
if (hasUpdate) {
if (actionText == '升级') {
controller.upgradeWakeVoiceTo(item);
} else {
} else if (actionText == '切换') {
controller.switchWakeVoiceTo(item);
}
},

25
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt

@ -421,7 +421,30 @@ class BleCommandSender {
callback?.notifyDeviceInfoReceived(cmdType, resultMap)
}
BleConst.CMD_GET_WAKEUP_VERSION -> {
val left0 = data[3].toInt() and 0xFF
val left1 = data[4].toInt() and 0xFF
val left2 = data[5].toInt() and 0xFF
val left3 = data[6].toInt() and 0xFF
val right0 = data[7].toInt() and 0xFF
val right1 = data[8].toInt() and 0xFF
val right2 = data[9].toInt() and 0xFF
val right3 = data[10].toInt() and 0xFF
val leftVersionDec = "$left0.$left1.$left2.$left3"
val rightVersionDec = "$right0.$right1.$right2.$right3"
val leftVersionHex = String.format("0x%02X.0x%02X.0x%02X.0x%02X", left0, left1, left2, left3)
val rightVersionHex = String.format("0x%02X.0x%02X.0x%02X.0x%02X", right0, right1, right2, right3)
val resultMap = mutableMapOf<String, Any>(
"success" to true,
"command" to cmdType,
"leftVersionDec" to leftVersionDec,
"rightVersionDec" to rightVersionDec,
"leftVersionHex" to leftVersionHex,
"rightVersionHex" to rightVersionHex
)
Log.d(TAG, "获取唤醒词版本号:leftVersionDec: $leftVersionDec,rightVersionDec: $rightVersionDec")
callback?.notifyDeviceInfoReceived(cmdType, resultMap)
}
else -> {
// 对于未知命令,返回原始数据
val resultMap = mapOf(

4
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt

@ -86,8 +86,8 @@ object BleConst {
const val CMD_VOLUME_UP = 0x06
/** 音量减小 */
const val CMD_VOLUME_DOWN = 0x07
/** A2DP播放 */
const val CMD_PLAY_A2DP = 0x08
/** 获取唤醒词版本 */
const val CMD_GET_WAKEUP_VERSION = 0x08
/** 唤醒指令 */
const val CMD_WAKE_UP = 0x11

24
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -837,6 +837,7 @@ object BleService {
}
g.discoverServices()
}
newState == BluetoothProfile.STATE_DISCONNECTED -> {
@ -934,7 +935,9 @@ object BleService {
setupNotifications(g, callAudioChar)
setupNotifications(g, audioChar)
Log.i(TAG, "通话音频服务特征找到并设置通知")
Handler(Looper.getMainLooper()).postDelayed({
getDeviceWakeupVersion()
}, 1000) // 延迟3秒再请求版本,确保通知描述符写入完成
} else {
Log.w(TAG, "通话音频服务特征未找到")
}
@ -954,6 +957,7 @@ object BleService {
}
// 在服务发现完成后,请求最大MTU
//requestMaxMtu(g)
}
override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) {
@ -1520,8 +1524,9 @@ object BleService {
* @return 是否成功发送命令
*/
fun playA2DP(): Boolean {
Log.i(TAG, "A2DP播放...")
return commandSender.sendCommand(BleConst.CMD_PLAY_A2DP.toByte())
// Log.i(TAG, "A2DP播放...")
// return commandSender.sendCommand(BleConst.CMD_PLAY_A2DP.toByte())
return true
}
/**
* 打开编码器
@ -1585,6 +1590,19 @@ object BleService {
return commandSender.sendCommand(BleConst.CMD_VOLUME_DOWN.toByte())
}
/**
* 获取设备唤醒词版本
* @return 是否成功发送命令
*/
fun getDeviceWakeupVersion(): Boolean {
Log.i(TAG, "获取设备唤醒词版本...")
val cmdData = byteArrayOf(
0xFF.toByte()
)
return commandSender.sendCommand(BleConst.CMD_GET_WAKEUP_VERSION.toByte(),cmdData)
}
// ======================================================================================================
// 连接状态和工具方法
// ======================================================================================================

25
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleWakeupVoicesUpgradeSender.kt

@ -115,6 +115,7 @@ class BleWakeupVoicesUpgradeSender {
this.callback = callback
}
/**
* 发送命令到设备 - 通用方法
* @param cmdType 命令类型
@ -229,6 +230,27 @@ class BleWakeupVoicesUpgradeSender {
// 握手/数据包响应都是以0xBB为头
if (header == (BleConst.DEVICE_RESPONSE_HEADER.toInt() and 0xFF)) {
when (cmd) {
0x08 -> { // 版本号查询回复: 0xBB + 0x08 + 0x08 + 左耳版本号(4Byte) + 右耳版本号(4Byte) + CRC
Log.d(TAG, "liwei------------- 版本号查询回复: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}")
if (data.size >= 12) {
val left0 = data[3].toInt() and 0xFF
val left1 = data[4].toInt() and 0xFF
val left2 = data[5].toInt() and 0xFF
val left3 = data[6].toInt() and 0xFF
val right0 = data[7].toInt() and 0xFF
val right1 = data[8].toInt() and 0xFF
val right2 = data[9].toInt() and 0xFF
val right3 = data[10].toInt() and 0xFF
val leftVersionDec = "$left0.$left1.$left2.$left3"
val rightVersionDec = "$right0.$right1.$right2.$right3"
val leftVersionHex = String.format("0x%02X.0x%02X.0x%02X.0x%02X", left0, left1, left2, left3)
val rightVersionHex = String.format("0x%02X.0x%02X.0x%02X.0x%02X", right0, right1, right2, right3)
Log.i(TAG, "liwei------------- 左耳版本号: $leftVersionDec ($leftVersionHex)")
Log.i(TAG, "liwei------------- 右耳版本号: $rightVersionDec ($rightVersionHex)")
} else {
Log.e(TAG, "liwei------------- 版本号查询回复数据长度异常: ${data.size}")
}
}
0x20 -> { // 握手确认: 0xBB + 0x20 + 0x05 + OTA包总字节数(4Byte) + 0x01 + CRC
Log.d(TAG, "liwei------------- 握手响应成功")
handshakeAcked = true
@ -442,9 +464,6 @@ class BleWakeupVoicesUpgradeSender {
return crc.toByte()
}
// ==========================================
// OTA封包/发送辅助
// ==========================================

Loading…
Cancel
Save