Browse Source

Merge commit '183b1bd4b5d330b77b207d8831457715c85c60d4' into lxm

newdev_shunjiawei
lxm 1 year ago
parent
commit
d791534147
  1. 142
      lib/data/services/ble_manager.dart
  2. 2
      lib/modules/agent/controllers/agent_controller.dart
  3. 156
      lib/modules/home/controllers/home_controller.dart
  4. 14
      lib/modules/home/views/home_view.dart
  5. 170
      lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
  6. 0
      lib/modules/ota_upgrade/model/ota_upgrade_model.dart
  7. 200
      lib/modules/ota_upgrade/views/ota_upgrade_view.dart
  8. 60
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  9. 10
      local_plugins/ble_service/ios/Classes/BleService.swift
  10. 8
      local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift
  11. 11
      local_plugins/ble_service/lib/ble_service.dart
  12. 20
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt

142
lib/data/services/ble_manager.dart

@ -9,13 +9,15 @@ import 'package:deep_voice/data/models/appconfig_model.dart';
import 'dart:convert';
import 'package:flutter/services.dart' show rootBundle;
import 'package:agent_service/agent_service.dart';
import 'package:classic_bluetooth/classic_bluetooth.dart';
/// 命令类型常量(与原生层保持一致)
class BleCommands {
static const int GET_VERSION = 0x01;
static const int GET_BATTERY_INFO = 0x02;
static const int CONTROL_CODEC = 0x03;
static const int PLAY_A2DP = 0x04;
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;
}
/// 蓝牙设备信息缓存模型
@ -37,6 +39,8 @@ class DeviceInfo {
final bool caseCharging;
final String firmwareVersion;
final String firmwareProductId;
final String firmwareProductColor;
final DateTime lastUpdated;
DeviceInfo({
@ -50,6 +54,8 @@ class DeviceInfo {
this.rightEarCharging = false,
this.caseCharging = false,
this.firmwareVersion = '',
this.firmwareProductId = '',
this.firmwareProductColor = '',
DateTime? lastUpdated,
}) : lastUpdated = lastUpdated ?? DateTime.now();
@ -65,6 +71,8 @@ class DeviceInfo {
bool? rightEarCharging,
bool? caseCharging,
String? firmwareVersion,
String? firmwareProductId,
String? firmwareProductColor,
}) {
return DeviceInfo(
address: address ?? this.address,
@ -77,6 +85,8 @@ class DeviceInfo {
rightEarCharging: rightEarCharging ?? this.rightEarCharging,
caseCharging: caseCharging ?? this.caseCharging,
firmwareVersion: firmwareVersion ?? this.firmwareVersion,
firmwareProductId: firmwareProductId ?? this.firmwareProductId,
firmwareProductColor: firmwareProductColor ?? this.firmwareProductColor,
lastUpdated: DateTime.now(),
);
}
@ -96,6 +106,7 @@ class BleManager extends GetxService {
// BLE服务实例
final BleService _bleService = BleService();
// 经典蓝牙实例
// BLE连接状态
final _connectionState = BleConnectionState.disconnected.obs;
@ -106,8 +117,7 @@ class BleManager extends GetxService {
bool get isConnected => _isConnected.value;
// 设备信息缓存
final Rx<DeviceInfo?> _deviceInfo = Rx<DeviceInfo?>(null);
DeviceInfo? get deviceInfo => _deviceInfo.value;
final deviceInfo = Rx<DeviceInfo?>(null); // 使用 Rx 包装
// 订阅
StreamSubscription<dynamic>? _statusSubscription;
@ -145,8 +155,8 @@ class BleManager extends GetxService {
if (event is Map) {
// 处理连接状态更新
if (event.containsKey('connectionState')) {
final state = event['connectionState'] as int;
if (event['type'] == 'connectionStateChanged') {
final state = event['state'] as int;
_connectionState.value = state;
// 连接状态转换
@ -154,8 +164,13 @@ class BleManager extends GetxService {
Logger.info('BLE设备已连接');
_isConnected.value = true;
// 在连接成功后获取设备信息
_fetchDeviceInfo();
// 在连接成功后延迟 300 毫秒获取设备信息(可根据需要调整时间)
Future.delayed(const Duration(milliseconds: 300), () {
// 可选:检查是否仍处于连接状态
if (_isConnected.value) {
fetchDeviceInfo();
}
});
} else if (state == BleConnectionState.disconnected ||
state == BleConnectionState.error) {
Logger.info('BLE设备已断开或发生错误');
@ -218,12 +233,18 @@ class BleManager extends GetxService {
void _processDeviceInfoByCommandId(
int commandId, Map<dynamic, dynamic> data) {
switch (commandId) {
case BleCommands.GET_BATTERY_INFO:
_updateBatteryInfoFromData(data);
break;
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;
default:
Logger.info('未处理的命令ID: $commandId');
}
@ -239,6 +260,11 @@ class BleManager extends GetxService {
case 'versionInfo':
_updateVersionInfoFromData(data);
break;
case 'productId':
_updateProductIdFromData(data);
break;
case 'productColor':
break;
default:
Logger.info('未处理的命令名称: $commandName');
}
@ -307,6 +333,29 @@ class BleManager extends GetxService {
}
}
/// 更新设备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');
}
}
/// 更新设备信息(如果已存在设备)
void _updateDeviceInfoIfNeeded({
int? batteryLevel,
@ -317,31 +366,53 @@ class BleManager extends GetxService {
bool? rightEarCharging,
bool? caseCharging,
String? firmwareVersion,
String? firmwareProductId,
String? firmwareProductColor,
}) {
// 如果设备信息已存在,则更新
if (_deviceInfo.value != null) {
_deviceInfo.value = _deviceInfo.value!.copyWith(
batteryLevel: batteryLevel,
leftEarBattery: leftEarBattery,
rightEarBattery: rightEarBattery,
caseBattery: caseBattery,
leftEarCharging: leftEarCharging,
rightEarCharging: rightEarCharging,
caseCharging: caseCharging,
firmwareVersion: firmwareVersion,
if (deviceInfo.value != null) {
deviceInfo.value = deviceInfo.value!.copyWith(
batteryLevel: batteryLevel ?? deviceInfo.value?.batteryLevel,
leftEarBattery: leftEarBattery ?? deviceInfo.value?.leftEarBattery,
rightEarBattery: 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,
);
}
// 创建设备信息
// 如果设备信息不存在,但有地址和名称,则创建新的设备信息
else if (_deviceInfo.value == null &&
else if (deviceInfo.value == null &&
_isConnected.value &&
(firmwareVersion != null || batteryLevel != null)) {
// 提示尝试创建设备信息但缺少必要信息
Logger.warning('尝试更新设备信息,但设备基本信息不存在');
deviceInfo.value = DeviceInfo(
address: "", // 应确保从连接信息中获取真实值
name: "",
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 ?? '',
);
}
}
/// 连接成功后获取设备信息
Future<void> _fetchDeviceInfo() async {
Future<void> fetchDeviceInfo() async {
Logger.info('开始获取BLE设备详细信息');
try {
@ -360,6 +431,14 @@ class BleManager extends GetxService {
} else {
Logger.warning('版本信息获取指令发送失败');
}
// 获取产品id
final productIdResult = await _bleService.getProductId();
if (productIdResult) {
Logger.info('产品id获取指令已发送');
} else {
Logger.warning('产品id获取指令发送失败');
}
} catch (e) {
Logger.error('获取BLE设备信息失败: $e');
}
@ -450,9 +529,9 @@ class BleManager extends GetxService {
_connectionState.value = BleConnectionState.connected;
// 如果设备信息存在,则获取详细信息
if (_deviceInfo.value != null) {
if (deviceInfo.value != null) {
// 获取详细设备信息
_fetchDeviceInfo();
fetchDeviceInfo();
}
}
} else {
@ -486,7 +565,6 @@ class BleManager extends GetxService {
}
}
/// 获取电池电量
Future<int> getBatteryLevel() async {
try {
@ -507,6 +585,16 @@ class BleManager extends GetxService {
}
}
/// 主动产品id
Future<bool> getProductId() async {
try {
return await _bleService.getProductId();
} catch (e) {
Logger.error('获取设备版本失败: $e');
return false;
}
}
/// 主动获取设备版本信息
Future<bool> fetchDeviceVersion() async {
try {

2
lib/modules/agent/controllers/agent_controller.dart

@ -621,7 +621,7 @@ class AgentController extends GetxController {
// 播放音乐
void musicStatus() async {
var status = await _musiceManager.getStatus();
logger.i('获取当前音乐播放状态 $status');
// logger.i('获取当前音乐播放状态 $status');
isPlaying.value = status['isPlaying'];
currentUrl.value = status['url'];
}

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

@ -19,7 +19,7 @@ enum ConnectionStatus {
/// 主页控制器,管理设备连接状态和信息展示
class HomeController extends GetxController {
static HomeController get to => Get.find();
// 依赖的控制器
final ExploreController exploreController = Get.find<ExploreController>();
@ -27,31 +27,47 @@ class HomeController extends GetxController {
final _connectionStatus = ConnectionStatus.disconnected.obs;
final _connectionErrorMsg = ''.obs;
final _bluetoothState = 'unknown'.obs;
// Getter方法
ConnectionStatus get connectionStatus => _connectionStatus.value;
String get connectionErrorMsg => _connectionErrorMsg.value;
String get bluetoothState => _bluetoothState.value;
BluetoothDevice? get currentDevice => _bluetoothManager.currentDevice;
DeviceInfo? get deviceInfo => _bleManager.deviceInfo;
Rx<DeviceInfo?> get deviceInfo => _bleManager.deviceInfo;
bool get isConnected => currentDevice != null;
// 设备详细信息的getter,从deviceInfo获取数据
int get leftEarBattery => deviceInfo?.leftEarBattery ?? 0;
int get rightEarBattery => deviceInfo?.rightEarBattery ?? 0;
int get caseBattery => deviceInfo?.caseBattery ?? 0;
bool get leftEarCharging => deviceInfo?.leftEarCharging ?? false;
bool get rightEarCharging => deviceInfo?.rightEarCharging ?? false;
bool get caseCharging => deviceInfo?.caseCharging ?? false;
String get firmwareVersion => deviceInfo?.firmwareVersion ?? '';
final leftEarBattery = 0.obs;
final rightEarBattery = 0.obs;
final caseBattery = 0.obs;
final leftEarCharging = false.obs;
final rightEarCharging = false.obs;
final caseCharging = false.obs;
final firmwareVersion = ''.obs;
// 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 ?? '';
/// 当有新的deviceInfo时更新所有Observable变量
void updateDeviceInfo(DeviceInfo? newDeviceInfo) {
leftEarBattery.value = newDeviceInfo?.leftEarBattery ?? 0;
rightEarBattery.value = newDeviceInfo?.rightEarBattery ?? 0;
caseBattery.value = newDeviceInfo?.caseBattery ?? 0;
leftEarCharging.value = newDeviceInfo?.leftEarCharging ?? false;
rightEarCharging.value = newDeviceInfo?.rightEarCharging ?? false;
caseCharging.value = newDeviceInfo?.caseCharging ?? false;
firmwareVersion.value = newDeviceInfo?.firmwareVersion ?? '';
}
// 监听和订阅
Worker? _deviceInfoWorker;
Worker? _bluetoothStateWorker;
Worker? _deviceWorker;
// 蓝牙服务
final _bluetoothManager = Get.find<BluetoothManager>();
final _bleManager = Get.find<BleManager>();
@ -60,7 +76,7 @@ class HomeController extends GetxController {
super.onInit();
_initWithCachedInfo();
}
@override
void onClose() {
_deviceInfoWorker?.dispose();
@ -73,11 +89,12 @@ class HomeController extends GetxController {
void _initWithCachedInfo() {
// 使用蓝牙服务中的当前状态
_bluetoothState.value = _bluetoothManager.bluetoothState;
// 根据当前设备状态设置连接状态
if (currentDevice != null) {
_connectionStatus.value = ConnectionStatus.connected;
_connectionErrorMsg.value = '';
updateDeviceInfo(_bleManager.deviceInfo.value);
Logger.info('主页加载缓存设备信息: ${currentDevice!.name}');
} else {
_connectionStatus.value = ConnectionStatus.disconnected;
@ -87,67 +104,73 @@ class HomeController extends GetxController {
_connectionErrorMsg.value = '未连接到设备';
}
}
// 设置监听器监听蓝牙服务状态变化
_setupStateListeners();
}
/// 设置状态监听器
void _setupStateListeners() {
// 设备信息监听 - 设备信息更新时自动触发UI更新
_deviceInfoWorker = ever(_bleManager.deviceInfo.obs, (DeviceInfo? info) {
_deviceInfoWorker = ever(_bleManager.deviceInfo, (DeviceInfo? info) {
Logger.info('设备信息监听');
if (info != null) {
Logger.info('主页更新设备信息: ${info.toString()}');
updateDeviceInfo(info);
} else {
Logger.info('主页清空设备信息');
}
update();
});
// 蓝牙状态监听
_bluetoothStateWorker = ever(_bluetoothManager.bluetoothState.obs, (String state) {
_bluetoothStateWorker =
ever(_bluetoothManager.bluetoothState.obs, (String state) {
_bluetoothState.value = state;
// 如果蓝牙关闭,更新错误信息
if (state == 'off') {
_connectionErrorMsg.value = '蓝牙未开启';
_connectionStatus.value = ConnectionStatus.disconnected;
}
update();
});
// 设备连接状态监听 - 直接监听设备变化
_deviceWorker = ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) {
_deviceWorker =
ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) {
if (device != null) {
// 设备已连接
if (_connectionStatus.value != ConnectionStatus.connected) {
_connectionStatus.value = ConnectionStatus.connected;
_connectionErrorMsg.value = '';
Logger.info('主页检测到设备已连接: ${device.name}');
}
} else if (_connectionStatus.value == ConnectionStatus.connected) {
// 设备已断开
_connectionStatus.value = ConnectionStatus.disconnected;
_connectionErrorMsg.value = '设备已断开连接';
Logger.info('主页检测到设备已断开连接');
}
update();
});
}
/// 刷新设备连接状态
/// 注意:此方法不会主动刷新设备,只更新UI状态
Future<void> refreshDeviceConnection() async {
// 更新当前状态以反映UI变化
bool hasDevice = currentDevice != null;
if (hasDevice) {
_connectionStatus.value = ConnectionStatus.connected;
_connectionErrorMsg.value = '';
await _bleManager.fetchDeviceInfo();
Logger.info('使用缓存设备信息: ${currentDevice!.name}');
} else {
if (_bluetoothState.value == 'off') {
@ -157,13 +180,13 @@ class HomeController extends GetxController {
}
_connectionStatus.value = ConnectionStatus.disconnected;
}
// 刷新UI
update();
}
// 导航和功能方法
/// 重新扫描设备
void rescanDevices() {
Get.toNamed(Routes.pairing);
@ -185,7 +208,7 @@ class HomeController extends GetxController {
}
// UI辅助方法
/// 获取连接状态对应的颜色
Color getConnectionStatusColor() {
switch (_connectionStatus.value) {
@ -244,62 +267,71 @@ class HomeController extends GetxController {
/// 获取设备ID
String getDeviceId() {
return deviceInfo?.address ?? currentDevice?.address ?? 'unknown'.tr;
return deviceInfo.value?.address ?? currentDevice?.address ?? 'unknown'.tr;
}
/// 获取设备电量文本
String getBatteryText() {
return deviceInfo?.batteryLevel != null && deviceInfo!.batteryLevel > 0
? '${deviceInfo!.batteryLevel}%'
: 'unknown'.tr;
return deviceInfo.value?.batteryLevel != null &&
deviceInfo.value!.batteryLevel > 0
? '${deviceInfo.value?.batteryLevel}%'
: 'unknown'.tr;
}
/// 获取耳机左右电量文本
String getEarbudsText() {
if (deviceInfo == null || (deviceInfo!.leftEarBattery <= 0 && deviceInfo!.rightEarBattery <= 0)) {
if (deviceInfo == null ||
(deviceInfo.value!.leftEarBattery <= 0 &&
deviceInfo.value!.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;
String leftStatus = deviceInfo.value!.leftEarBattery > 0
? '${deviceInfo.value!.leftEarBattery}%'
: 'N/A';
String rightStatus = deviceInfo.value!.rightEarBattery > 0
? '${deviceInfo.value!.rightEarBattery}%'
: 'N/A';
leftStatus =
deviceInfo.value!.leftEarCharging ? '$leftStatus ⚡' : leftStatus;
rightStatus =
deviceInfo.value!.rightEarCharging ? '$rightStatus ⚡' : rightStatus;
return '左耳: $leftStatus / 右耳: $rightStatus';
}
/// 获取充电仓电量文本
String getCaseText() {
if (deviceInfo == null || deviceInfo!.caseBattery <= 0) {
if (deviceInfo.value == null || deviceInfo.value!.caseBattery <= 0) {
return 'unknown'.tr;
}
String caseStatus = '${deviceInfo!.caseBattery}%';
if (deviceInfo!.caseCharging) {
String caseStatus = '${deviceInfo.value!.caseBattery}%';
if (deviceInfo.value!.caseCharging) {
caseStatus = '$caseStatus ⚡';
}
return '充电仓: $caseStatus';
}
/// 获取充电状态文本
String getChargingText() {
if (deviceInfo == null) {
return 'unknown'.tr;
}
bool isCharging = deviceInfo!.leftEarCharging ||
deviceInfo!.rightEarCharging ||
deviceInfo!.caseCharging;
bool isCharging = deviceInfo.value!.leftEarCharging ||
deviceInfo.value!.rightEarCharging ||
deviceInfo.value!.caseCharging;
return isCharging ? 'charging'.tr : 'notCharging'.tr;
}
/// 获取设备版本文本
String getVersionText() {
return deviceInfo?.firmwareVersion.isNotEmpty == true ?
deviceInfo!.firmwareVersion : 'unknown'.tr;
return deviceInfo.value?.firmwareVersion.isNotEmpty == true
? deviceInfo.value!.firmwareVersion
: 'unknown'.tr;
}
}

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

@ -458,8 +458,8 @@ class HomeView extends GetView<HomeController> {
_buildBatteryGridItem(
icon: Icons.headphones,
label: '左耳机',
batteryLevel: leftBattery,
isCharging: leftEarCharging,
batteryLevel: leftBattery.value,
isCharging: leftEarCharging.value,
isDarkMode: isDarkMode,
),
@ -467,8 +467,8 @@ class HomeView extends GetView<HomeController> {
_buildBatteryGridItem(
icon: Icons.headphones,
label: '右耳机',
batteryLevel: rightBattery,
isCharging: rightEarCharging,
batteryLevel: rightBattery.value,
isCharging: rightEarCharging.value,
isDarkMode: isDarkMode,
),
@ -476,8 +476,8 @@ class HomeView extends GetView<HomeController> {
_buildBatteryGridItem(
icon: Icons.charging_station,
label: '充电盒',
batteryLevel: caseBattery,
isCharging: caseCharging,
batteryLevel: caseBattery.value,
isCharging: caseCharging.value,
isDarkMode: isDarkMode,
),
],
@ -620,7 +620,7 @@ class HomeView extends GetView<HomeController> {
_buildInfoRow(
icon: Icons.info_outline,
label: 'firmwareVersion'.tr,
value: controller.firmwareVersion,
value: controller.firmwareVersion.value,
isDarkMode: isDarkMode,
),

170
lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart

@ -1,19 +1,17 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:path/path.dart';
import 'dart:async';
import 'dart:convert';
import 'package:get_storage/get_storage.dart';
import '../../../data/services/network/api.dart';
import '../ota.dart';
import '../model/ota_upgrade_model.dart';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:json_annotation/json_annotation.dart';
import '../../../data/services/ble_manager.dart';
class OtaUpgradeController extends GetxController {
final RxString errorMessage = ''.obs;
final RxBool isFetchingDeviceInfo = false.obs;
final Dio _dio = Dio();
final RxString latestVersion = ''.obs;
final RxString currentVersion = ''.obs;
@ -24,41 +22,93 @@ class OtaUpgradeController extends GetxController {
final RxDouble progress = 0.0.obs;
// 蓝牙服务
final _bleManager = Get.find<BleManager>();
@override
void onInit() {
super.onInit();
_getCurrentVersion();
}
// 获取当前版本信息
// 添加重试方法
void retryConnection() {
errorMessage.value = '';
_getCurrentVersion();
}
// 添加更新方法
Future<void> startUpdate() async {
try {
isUpdating.value = true;
// 实现更新逻辑...
} catch (e) {
errorMessage.value = e.toString();
Get.snackbar('更新失败', e.toString());
} finally {
isUpdating.value = false;
}
}
Future<void> _getCurrentVersion() async {
//发送ble获取固件版本
var firmwareVersion = "1.1.0";
currentVersion.value = firmwareVersion;
//发送ble获取产品id
var id = "a2";
//发送api请求
final response = await Api.getequipmentinfo({
"id": id,
});
if (response['info'] != '') {
print('getLoginToken:进来了');
// 根据API设计调整
final resp = OtaConfigResp.fromJson(response);
try {
// 添加获取设备信息的重试机制
DeviceInfo? deviceInfo = _bleManager.deviceInfo.value;
// 第一次检查设备信息
if (deviceInfo == null) {
// 显示加载提示
isFetchingDeviceInfo.value = true;
// 尝试重新获取设备信息
await _bleManager.fetchDeviceInfo();
// 等待后再次获取设备信息
deviceInfo = _bleManager.deviceInfo.value;
isFetchingDeviceInfo.value = false;
// 仍然为空则抛出异常
if (deviceInfo == null) {
throw const FormatException('无法获取设备信息,请检查设备连接');
}
}
// 检查固件信息有效性
if (deviceInfo.firmwareVersion.isEmpty
//||
// deviceInfo.firmwareProductId.isEmpty
) {
throw const FormatException('设备固件信息不完整');
}
// 正常流程
currentVersion.value = deviceInfo.firmwareVersion;
print('成功解析: ${resp.id}, ${resp.factory}');
// API请求
final response = await Api.getequipmentinfo({
"id": "a2",
});
// 处理API响应
if (response['info'] == null) {
throw const FormatException('API 响应格式错误');
}
final resp = OtaConfigResp.fromJson(response);
latestVersion.value = resp.systemversion!;
updatepackageaddress = resp.updatepackageaddress!;
if (_isNewVersionAvailable(resp.systemversion!)) {
//需要升级
print('需要更新');
isUpdateAvailable.value = false;
} else {
isUpdateAvailable.value = true;
//已经是最新版本更新ui,按钮隐藏
}
} else {
throw const FormatException('API 响应缺少 "info" 字段');
// 版本检查
isUpdateAvailable.value = _isNewVersionAvailable(resp.systemversion!);
} catch (e) {
// 统一错误处理
errorMessage.value = e.toString();
Get.snackbar('错误', e.toString(),
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white);
// 重要:重新抛出异常以便上层处理(可选)
} finally {
isFetchingDeviceInfo.value = false;
}
}
@ -120,13 +170,10 @@ class OtaUpgradeController extends GetxController {
);
}
// 开始下载
// 修改后的startDownload方法
Future<void> startDownload() async {
isUpdating.value = true;
// 查找最后一个 '/' 的位置
int lastIndex = updatepackageaddress.lastIndexOf('/');
// 截取最后一个 '/' 后面的字符串
String fileName = updatepackageaddress.substring(lastIndex + 1);
String directoryName = "ota_upgrades";
@ -145,14 +192,54 @@ class OtaUpgradeController extends GetxController {
throw FileSystemException('无法访问下载目录');
}
// 创建子目录:Download/APP
final targetDir = Directory('${downloadsDir.path}/$directoryName');
if (!await targetDir.exists()) {
await targetDir.create(recursive: true);
}
final savePath = '${targetDir.path}/$fileName';
print('文件保存路径: $savePath'); // 关键日志
// 新增:检查同名文件逻辑
bool existingSameFile = false;
try {
var files = await targetDir.list().toList();
for (var entity in files) {
if (await FileSystemEntity.isFile(entity.path)) {
String name = basename(entity.path); // 使用path包中的basename
if (name == fileName) {
existingSameFile = true;
break;
}
}
}
} catch (e) {
Get.snackbar('错误', '检查本地文件失败: $e');
isUpdating.value = false;
return;
}
if (existingSameFile) {
Get.snackbar('文件已存在', '跳过下载,直接更新');
isUpdating.value = false;
_installApp(savePath);
return;
}
// 新增:删除其他文件逻辑
try {
var files = await targetDir.list().toList();
for (var entity in files) {
if (await FileSystemEntity.isFile(entity.path)) {
await entity.delete();
}
}
} catch (e) {
Get.snackbar('错误', '删除旧文件失败: $e');
isUpdating.value = false;
return;
}
print('文件保存路径: $savePath');
final options = Options(
receiveTimeout: Duration.zero,
@ -181,22 +268,23 @@ class OtaUpgradeController extends GetxController {
},
);
// 下载完成后验证文件存在性
File file = File(savePath);
if (await file.exists()) {
Get.snackbar('下载完成', '文件已保存至: $savePath'); // 明确提示路径
Get.snackbar('下载完成', '文件已保存至: $savePath');
} else {
Get.snackbar('错误', '文件未找到,请检查写入权限');
}
isUpdating.value = false;
//下载完成,开始升级
_installApp(savePath);
} on DioException catch (e) {
Get.snackbar('下载失败', '错误: ${e.message}');
isUpdating.value = false;
} on FileSystemException catch (e) {
Get.snackbar('存储失败', '无法写入文件: ${e.message}');
isUpdating.value = false;
} catch (e) {
Get.snackbar('未知错误', e.toString());
isUpdating.value = false;
}
}

0
lib/modules/ota_upgrade/ota.dart → lib/modules/ota_upgrade/model/ota_upgrade_model.dart

200
lib/modules/ota_upgrade/views/ota_upgrade_view.dart

@ -4,106 +4,126 @@ import '../controllers/ota_upgrade_controller.dart';
class OtaUpgradeView extends GetView<OtaUpgradeController> {
const OtaUpgradeView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
// 添加外层Center
child: SingleChildScrollView(
// 添加滚动防止小屏溢出
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, // 水平居中
mainAxisSize: MainAxisSize.min, // 紧凑布局
children: [
// 设备图标
CircleAvatar(
radius: 60,
backgroundColor: Colors.blue[100],
child: Icon(
Icons.phone_iphone,
size: 60,
color: Colors.blue,
),
),
SizedBox(height: 30),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 600), // 限制最大宽度
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 设备图标
_buildDeviceIcon(),
SizedBox(height: 30),
// 版本信息
Column(
children: [
Text(
'当前版本',
style: TextStyle(
fontSize: 18,
color: Colors.grey,
),
),
SizedBox(height: 8),
Text(
'v${controller.currentVersion}',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
],
),
SizedBox(height: 40),
// 版本信息
_buildVersionInfo(),
SizedBox(height: 40),
// 更新按钮/进度条
Obx(() {
if (controller.isUpdating.value) {
return Column(
children: [
SizedBox(
width: 200, // 限制进度条宽度
child: LinearProgressIndicator(
value: controller.downloadProgress.value,
backgroundColor: Colors.grey[200],
valueColor:
AlwaysStoppedAnimation<Color>(Colors.blue),
minHeight: 12,
borderRadius: BorderRadius.circular(10),
),
),
SizedBox(height: 20),
Text(
'${(controller.downloadProgress.value * 100).toStringAsFixed(1)}%',
style: TextStyle(color: Colors.grey),
)
],
);
} else {
return ElevatedButton(
onPressed: controller.isUpdateAvailable.value
? null
: controller.checkUpdate,
style: ElevatedButton.styleFrom(
backgroundColor: controller.isUpdateAvailable.value
? Colors.grey
: Colors.blue,
foregroundColor: Colors.white,
padding:
EdgeInsets.symmetric(horizontal: 40, vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
child: Text(
controller.isUpdateAvailable.value ? '已是最新版本' : '立即更新',
style: TextStyle(fontSize: 16),
),
);
}
}),
],
// 状态显示区域
Obx(() {
if (controller.isFetchingDeviceInfo.value) {
return _buildLoadingIndicator();
}
if (controller.errorMessage.value.isNotEmpty) {
return _buildErrorInfo();
}
return _buildUpdateButton();
}),
],
),
),
),
),
),
);
}
// 组件化构建方法
Widget _buildDeviceIcon() => CircleAvatar(
radius: 60,
backgroundColor: Colors.blue[100],
child: Icon(Icons.phone_iphone, size: 60, color: Colors.blue),
);
Widget _buildVersionInfo() => Column(
children: [
Text('当前版本', style: TextStyle(fontSize: 18, color: Colors.grey)),
SizedBox(height: 8),
Text('v${controller.currentVersion}',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue)),
],
);
Widget _buildLoadingIndicator() => Column(
children: [
CircularProgressIndicator(),
SizedBox(height: 20),
Text('正在连接设备...', style: TextStyle(color: Colors.grey))
],
);
Widget _buildErrorInfo() => Column(
children: [
Icon(Icons.error_outline, color: Colors.red, size: 40),
SizedBox(height: 10),
Text(controller.errorMessage.value,
style: TextStyle(color: Colors.red)),
SizedBox(height: 20),
ElevatedButton(
onPressed: controller.retryConnection,
child: Text('重试连接'),
)
],
);
Widget _buildUpdateButton() {
return controller.isUpdating.value
? _buildProgressIndicator()
: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: controller.isUpdateAvailable.value
? Colors.blue
: Colors.grey,
padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
onPressed: controller.isUpdateAvailable.value
? controller.startUpdate
: null,
child: Text(
controller.isUpdateAvailable.value ? '立即更新' : '已是最新版本',
style: TextStyle(fontSize: 16, color: Colors.white),
),
);
}
Widget _buildProgressIndicator() => Column(
children: [
SizedBox(
width: 200,
child: LinearProgressIndicator(
value: controller.downloadProgress.value,
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
minHeight: 12,
borderRadius: BorderRadius.circular(10),
),
),
SizedBox(height: 20),
Text(
'${(controller.downloadProgress.value * 100).toStringAsFixed(1)}%',
style: TextStyle(color: Colors.grey))
],
);
}

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

@ -473,7 +473,7 @@ object BleService {
}
override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) {
val data = c.value ?: return
// 根据特征UUID区分处理
@ -1017,9 +1017,11 @@ object BleService {
stopOpusStreamDecoding() // 停止Opus解码流
return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(),
byteArrayOf(BleConst.CODEC_CONTROL_CLOSE.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()) // 或其他默认声道
byteArrayOf(
BleConst.CODEC_CONTROL_CLOSE.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
) // 或其他默认声道
)
}
@ -1027,45 +1029,54 @@ object BleService {
* 控制编解码 - 打开解码
*/
fun openDecoder(): Boolean {
FileLogger.i(TAG, "打开编码 (设备侧)...")
FileLogger.i(TAG, "打开编码 (设备侧)...")
val opusStarted = startOpusStreamDecoding(
hasHeader = false, // BLE传输的Opus数据通常无文件头
channel = 1, // 单声道
sampleRate = 16000,// Opus推荐16kHz或更高
packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM)
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
)
FileLogger.i(TAG, "打开解码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()))
return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
BleConst.CODEC_CONTROL_DECODE_ON.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
)
}
/**
* 控制编解码 - 打开解码
*/
fun openA2DPDecoder(): Boolean {
FileLogger.i(TAG, "打开编码 (设备侧)...")
FileLogger.i(TAG, "打开编码 (设备侧)...")
val opusStarted = startOpusStreamDecoding(
hasHeader = false, // BLE传输的Opus数据通常无文件头
channel = 1, // 单声道
sampleRate = 16000,// Opus推荐16kHz或更高
packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM)
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
)
FileLogger.i(TAG, "打开解码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()))
return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
)
}
/**
@ -1087,11 +1098,11 @@ object BleService {
channel = 1, // 单声道
sampleRate = 16000,// Opus推荐16kHz或更高
packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM)
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
)
// 注意:此方法通常是控制设备端开始编码并发送数据。
// App侧通常是打开解码器(openDecoder)来接收和处理这些数据。
@ -1178,7 +1189,6 @@ object BleService {
return bluetoothAdapter
}
// ======================================================================================================
// 资源清理

10
local_plugins/ble_service/ios/Classes/BleService.swift

@ -353,6 +353,11 @@ actor BleService {
func getDeviceVersion() -> Bool {
return sendCommand(BleConst.CMD_GET_VERSION)
}
///获取产品ID
/// - Returns: 是否成功发送请求
func getProductId() -> Bool {
return sendCommand(BleConst.CMD_GET_PRODUCT_ID)
}
/// 获取电池信息
/// - Returns: 是否成功发送请求
@ -371,6 +376,11 @@ actor BleService {
func openDecoder() -> Bool {
return sendCodecControl(BleConst.CODEC_CONTROL_DECODE_ON)
}
/// 打开解码器
/// - Returns: 是否成功发送请求
func openA2DPDecoder() -> Bool {
return sendCodecControl(BleConst.CODEC_CONTROL_A2DP_PLAY)
}
/// 关闭编解码器
/// - Returns: 是否成功发送请求

8
local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift

@ -73,12 +73,18 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin, BleDelegate {
case "getDeviceVersion":
result(bleService.getDeviceVersion())
case "getProductId":
result(bleService.getProductId())
case "openEncoder":
result(bleService.openEncoder())
case "openDecoder":
result(bleService.openDecoder())
case "openA2DPDecoder":
result(bleService.openA2DPDecoder())
case "closeCodec":
result(bleService.closeCodec())

11
local_plugins/ble_service/lib/ble_service.dart

@ -83,6 +83,17 @@ class BleService {
}
}
/// 获取产品ID
Future<bool> getProductId() async {
try {
final result = await _methodChannel.invokeMethod<bool>('getProductId');
return result ?? false;
} catch (e) {
print('获取设备版本失败: $e');
return false;
}
}
/// 获取电池信息
Future<bool> getBatteryInfo() async {
try {

20
local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt

@ -24,22 +24,33 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
FileLogger.d(TAG, "Method call: ${call.method}")
val intent = Intent(context, MusicService::class.java)
when (call.method) {
"play" -> {
val url = call.argument<String>("url")
MusicServiceStarter.startServiceWithCommand(context, "play", url)
result.success(null)
}
"playlist" -> {
val urls = call.argument<List<String>>("urls")
MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls)
result.success(null)
}
"pause" -> {
MusicServiceStarter.startServiceWithCommand(context, "pause")
result.success(null)
}
"resume" -> {
MusicServiceStarter.startServiceWithCommand(context, "resume")
result.success(null)
}
"stop" -> {
MusicServiceStarter.startServiceWithCommand(context, "stop")
result.success(null)
}
"pause" -> MusicServiceStarter.startServiceWithCommand(context, "pause")
"resume" -> MusicServiceStarter.startServiceWithCommand(context, "resume")
"stop" -> MusicServiceStarter.startServiceWithCommand(context, "stop")
"setVolume" -> {
val volume = call.argument<Double>("volume")?.toFloat()
MusicServiceStarter.startServiceWithCommand(context, "setVolume", volume = volume)
result.success(null)
}
"getStatus" -> {
val isPlaying = MusicService.isPlaying()
@ -52,7 +63,6 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler {
}
else -> result.notImplemented()
}
result.success(null)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {}

Loading…
Cancel
Save