Commit 45154e91 authored by akari's avatar akari

fix: 修复蓝牙操作BUG

parent 2160220c
...@@ -88,10 +88,12 @@ class BleBluetoothManager<T> { ...@@ -88,10 +88,12 @@ class BleBluetoothManager<T> {
String? _connectedDeviceId; String? _connectedDeviceId;
QualifiedCharacteristic? _notifyCharacteristic; QualifiedCharacteristic? _notifyCharacteristic;
QualifiedCharacteristic? _writeCharacteristic; QualifiedCharacteristic? _writeCharacteristic;
bool _writeCharacteristicOnlyWithoutResponse = false;
StreamSubscription<DiscoveredDevice>? _scanSubscription; StreamSubscription<DiscoveredDevice>? _scanSubscription;
StreamSubscription<ConnectionStateUpdate>? _connectionSubscription; StreamSubscription<ConnectionStateUpdate>? _connectionSubscription;
StreamSubscription<List<int>>? _notifySubscription; StreamSubscription<List<int>>? _notifySubscription;
Timer? _scanTimer; Timer? _scanTimer;
Future<void> _writeQueue = Future<void>.value();
final Map<String, BluetoothScanDevice> _scannedDevices = {}; final Map<String, BluetoothScanDevice> _scannedDevices = {};
...@@ -137,7 +139,7 @@ class BleBluetoothManager<T> { ...@@ -137,7 +139,7 @@ class BleBluetoothManager<T> {
final nextStatus = await _ble.statusStream final nextStatus = await _ble.statusStream
.where((value) => value != BleStatus.unknown) .where((value) => value != BleStatus.unknown)
.first .first
.timeout(const Duration(seconds: 2), onTimeout: () => status); .timeout(const Duration(seconds: 8), onTimeout: () => status);
if (nextStatus != BleStatus.ready) { if (nextStatus != BleStatus.ready) {
debugPrint( debugPrint(
...@@ -258,13 +260,14 @@ class BleBluetoothManager<T> { ...@@ -258,13 +260,14 @@ class BleBluetoothManager<T> {
debugPrint( debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_to_device_id empty_device_id'); '🔥🔥🔥 BLE_CONNECT_DEBUG connect_to_device_id empty_device_id');
_notifyMessage('无效的蓝牙设备地址'); _notifyMessage('无效的蓝牙设备地址');
return; throw ArgumentError('无效的蓝牙设备地址');
} }
if (!await ensureBluetoothReady()) { if (!await ensureBluetoothReady()) {
debugPrint( debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_to_device_id bluetooth_not_ready'); '🔥🔥🔥 BLE_CONNECT_DEBUG connect_to_device_id bluetooth_not_ready');
return; _setStatus(BluetoothConnectionStatus.disconnected);
throw StateError('蓝牙未就绪或未授权');
} }
await disconnect(); await disconnect();
...@@ -306,11 +309,31 @@ class BleBluetoothManager<T> { ...@@ -306,11 +309,31 @@ class BleBluetoothManager<T> {
_notifyMessage('蓝牙连接成功: $deviceId'); _notifyMessage('蓝牙连接成功: $deviceId');
debugPrint( debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG state_connected deviceId=$deviceId'); '🔥🔥🔥 BLE_CONNECT_DEBUG state_connected deviceId=$deviceId');
await _enableNotifications(deviceId); try {
await _discoverServicesAndResolveWriteCharacteristic(deviceId);
final notifyReady = await _enableNotifications(deviceId);
if (notifyReady) {
activateDataSource(); activateDataSource();
} else {
_notifyMessage('蓝牙已连接,但未找到可订阅的接收特征值');
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG connected_without_notify '
'deviceId=$deviceId',
);
}
if (!completer.isCompleted) { if (!completer.isCompleted) {
completer.complete(); completer.complete();
} }
} catch (error) {
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG connected_setup_error '
'deviceId=$deviceId error=$error',
);
_notifyMessage('蓝牙连接初始化失败: $error');
if (!completer.isCompleted) {
completer.completeError(error);
}
}
break; break;
case DeviceConnectionState.disconnecting: case DeviceConnectionState.disconnecting:
_setStatus(BluetoothConnectionStatus.disconnected); _setStatus(BluetoothConnectionStatus.disconnected);
...@@ -362,7 +385,21 @@ class BleBluetoothManager<T> { ...@@ -362,7 +385,21 @@ class BleBluetoothManager<T> {
} }
} }
Future<void> writeData(List<int> data, {bool withoutResponse = false}) async { Future<void> writeData(List<int> data, {bool withoutResponse = false}) {
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG enqueue data=$data hex=${bytesToHex(data)}',
);
_writeQueue = _writeQueue.then(
(_) => _writeDataLocked(data, withoutResponse: withoutResponse),
onError: (_) => _writeDataLocked(data, withoutResponse: withoutResponse),
);
return _writeQueue;
}
Future<void> _writeDataLocked(
List<int> data, {
bool withoutResponse = false,
}) async {
if (data.isEmpty) { if (data.isEmpty) {
_notifyMessage('发送数据为空'); _notifyMessage('发送数据为空');
_sendResultController.add(false); _sendResultController.add(false);
...@@ -377,16 +414,19 @@ class BleBluetoothManager<T> { ...@@ -377,16 +414,19 @@ class BleBluetoothManager<T> {
} }
final hex = bytesToHex(data); final hex = bytesToHex(data);
final effectiveWithoutResponse =
withoutResponse || _writeCharacteristicOnlyWithoutResponse;
Log.warn( Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG write_data ' '🔥🔥🔥 BLE_WRITE_DEBUG write_data '
'deviceId=${characteristic.deviceId} ' 'deviceId=${characteristic.deviceId} '
'service=${characteristic.serviceId} ' 'service=${characteristic.serviceId} '
'characteristic=${characteristic.characteristicId} ' 'characteristic=${characteristic.characteristicId} '
'withoutResponse=$withoutResponse ' 'withoutResponse=$effectiveWithoutResponse '
'data=$data '
'hex=$hex', 'hex=$hex',
); );
try { try {
if (withoutResponse) { if (effectiveWithoutResponse) {
await _ble.writeCharacteristicWithoutResponse( await _ble.writeCharacteristicWithoutResponse(
characteristic, characteristic,
value: data, value: data,
...@@ -401,6 +441,8 @@ class BleBluetoothManager<T> { ...@@ -401,6 +441,8 @@ class BleBluetoothManager<T> {
Log.warn('🔥🔥🔥 BLE_WRITE_DEBUG write_error hex=$hex error=$error'); Log.warn('🔥🔥🔥 BLE_WRITE_DEBUG write_error hex=$hex error=$error');
_notifyMessage('蓝牙数据发送失败: $error'); _notifyMessage('蓝牙数据发送失败: $error');
_sendResultController.add(false); _sendResultController.add(false);
} finally {
await Future<void>.delayed(const Duration(milliseconds: 120));
} }
} }
...@@ -435,20 +477,23 @@ class BleBluetoothManager<T> { ...@@ -435,20 +477,23 @@ class BleBluetoothManager<T> {
await _sendResultController.close(); await _sendResultController.close();
} }
Future<void> _enableNotifications(String deviceId) async { Future<bool> _enableNotifications(String deviceId) async {
final characteristic = _notifyCharacteristic; final characteristic = _notifyCharacteristic;
if (characteristic == null) { if (characteristic == null) {
debugPrint( debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG notify_characteristic_null deviceId=$deviceId'); '🔥🔥🔥 BLE_CONNECT_DEBUG notify_characteristic_null deviceId=$deviceId');
throw StateError('接收特征值为空'); return false;
} }
try {
await _notifySubscription?.cancel(); await _notifySubscription?.cancel();
debugPrint( debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG notify_subscribe_start deviceId=$deviceId ' '🔥🔥🔥 BLE_CONNECT_DEBUG notify_subscribe_start deviceId=$deviceId '
'service=$serviceUuid characteristic=$notifyCharacteristicUuid', 'service=${characteristic.serviceId} '
'characteristic=${characteristic.characteristicId}',
); );
_notifySubscription = _ble.subscribeToCharacteristic(characteristic).listen( _notifySubscription =
_ble.subscribeToCharacteristic(characteristic).listen(
(bytes) { (bytes) {
if (!isActive || bytes.isEmpty) { if (!isActive || bytes.isEmpty) {
return; return;
...@@ -477,11 +522,126 @@ class BleBluetoothManager<T> { ...@@ -477,11 +522,126 @@ class BleBluetoothManager<T> {
); );
debugPrint( debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG notify_subscribe_done deviceId=$deviceId'); '🔥🔥🔥 BLE_CONNECT_DEBUG notify_subscribe_done deviceId=$deviceId');
return true;
} catch (error) {
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG notify_subscribe_error deviceId=$deviceId error=$error');
_notifyMessage('订阅蓝牙数据失败: $error');
return false;
}
}
Future<void> _discoverServicesAndResolveWriteCharacteristic(
String deviceId,
) async {
try {
Log.warn('🔥🔥🔥 BLE_DISCOVER_DEBUG start deviceId=$deviceId');
await _ble.discoverAllServices(deviceId);
final services = await _ble.getDiscoveredServices(deviceId);
QualifiedCharacteristic? fallbackWriteCharacteristic;
QualifiedCharacteristic? fallbackNotifyCharacteristic;
var fallbackOnlyWithoutResponse = false;
var preferredWriteFound = false;
var preferredNotifyFound = false;
for (final service in services) {
Log.warn('🔥🔥🔥 BLE_DISCOVER_DEBUG service=${service.id}');
for (final characteristic in service.characteristics) {
final isPreferredWrite =
_isSameUuid(characteristic.id, writeCharacteristicUuid);
final isPreferredNotify =
_isSameUuid(characteristic.id, notifyCharacteristicUuid);
final canWrite = characteristic.isWritableWithResponse ||
characteristic.isWritableWithoutResponse;
final canNotify =
characteristic.isNotifiable || characteristic.isIndicatable;
Log.warn(
'🔥🔥🔥 BLE_DISCOVER_DEBUG characteristic=${characteristic.id} '
'service=${service.id} '
'read=${characteristic.isReadable} '
'write=${characteristic.isWritableWithResponse} '
'writeNoResp=${characteristic.isWritableWithoutResponse} '
'notify=${characteristic.isNotifiable} '
'indicate=${characteristic.isIndicatable}'
'${isPreferredWrite ? ' preferredWrite=true' : ''}'
'${isPreferredNotify ? ' preferredNotify=true' : ''}',
);
final resolved = QualifiedCharacteristic(
deviceId: deviceId,
serviceId: service.id,
characteristicId: characteristic.id,
);
if (canNotify) {
fallbackNotifyCharacteristic ??= resolved;
if (isPreferredNotify) {
_notifyCharacteristic = resolved;
preferredNotifyFound = true;
}
}
if (!canWrite) continue;
final onlyWithoutResponse =
!characteristic.isWritableWithResponse &&
characteristic.isWritableWithoutResponse;
fallbackWriteCharacteristic ??= resolved;
fallbackOnlyWithoutResponse = onlyWithoutResponse;
if (isPreferredWrite) {
_writeCharacteristic = resolved;
_writeCharacteristicOnlyWithoutResponse = onlyWithoutResponse;
preferredWriteFound = true;
}
}
}
if (!preferredNotifyFound && fallbackNotifyCharacteristic != null) {
_notifyCharacteristic = fallbackNotifyCharacteristic;
Log.warn(
'🔥🔥🔥 BLE_DISCOVER_DEBUG preferred_notify_not_available '
'fallback=${_notifyCharacteristic!.characteristicId} '
'service=${_notifyCharacteristic!.serviceId}',
);
}
if (!preferredWriteFound && fallbackWriteCharacteristic != null) {
_writeCharacteristic = fallbackWriteCharacteristic;
_writeCharacteristicOnlyWithoutResponse = fallbackOnlyWithoutResponse;
Log.warn(
'🔥🔥🔥 BLE_DISCOVER_DEBUG preferred_write_not_writable '
'fallback=${_writeCharacteristic!.characteristicId} '
'service=${_writeCharacteristic!.serviceId} '
'onlyWithoutResponse=$_writeCharacteristicOnlyWithoutResponse',
);
}
final resolvedNotify = _notifyCharacteristic;
final resolvedWrite = _writeCharacteristic;
Log.warn(
'🔥🔥🔥 BLE_DISCOVER_DEBUG resolved_notify '
'characteristic=${resolvedNotify?.characteristicId} '
'service=${resolvedNotify?.serviceId}',
);
Log.warn(
'🔥🔥🔥 BLE_DISCOVER_DEBUG resolved_write '
'characteristic=${resolvedWrite?.characteristicId} '
'service=${resolvedWrite?.serviceId} '
'onlyWithoutResponse=$_writeCharacteristicOnlyWithoutResponse',
);
} catch (error) {
Log.warn(
'🔥🔥🔥 BLE_DISCOVER_DEBUG error deviceId=$deviceId error=$error',
);
}
} }
void _clearCharacteristics() { void _clearCharacteristics() {
_notifyCharacteristic = null; _notifyCharacteristic = null;
_writeCharacteristic = null; _writeCharacteristic = null;
_writeCharacteristicOnlyWithoutResponse = false;
} }
void _setStatus(BluetoothConnectionStatus status) { void _setStatus(BluetoothConnectionStatus status) {
...@@ -524,6 +684,10 @@ class BleBluetoothManager<T> { ...@@ -524,6 +684,10 @@ class BleBluetoothManager<T> {
return prefixes.any(value.startsWith); return prefixes.any(value.startsWith);
} }
static bool _isSameUuid(Uuid left, Uuid right) {
return left.toString().toLowerCase() == right.toString().toLowerCase();
}
static String _normalizeRemoteIdForSort(String remoteId) { static String _normalizeRemoteIdForSort(String remoteId) {
return remoteId.replaceAll(RegExp(r'[^0-9a-zA-Z]'), '').toUpperCase(); return remoteId.replaceAll(RegExp(r'[^0-9a-zA-Z]'), '').toUpperCase();
} }
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart'; import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/utils/bluetooth/index.dart';
typedef OnMenuItemTap = Function(String menuId); typedef OnMenuItemTap = Function(String menuId);
...@@ -77,15 +78,57 @@ class MonitoringBottomMenu extends StatelessWidget { ...@@ -77,15 +78,57 @@ class MonitoringBottomMenu extends StatelessWidget {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
// 图标 StreamBuilder<BluetoothConnectionStatus>(
Container( stream: appBluetoothManager.stateStream,
initialData: appBluetoothManager.status,
builder: (context, snapshot) {
final status = snapshot.data;
final isActive =
status == BluetoothConnectionStatus.connectedReady ||
status == BluetoothConnectionStatus.active;
return SizedBox(
width: 167.w, width: 167.w,
height: 62.h, height: 62.h,
child: AnimatedContainer(
duration: const Duration(milliseconds: 180),
padding: EdgeInsets.symmetric(
horizontal: isActive ? 7.w : 0,
vertical: isActive ? 4.h : 0,
),
decoration: isActive
? BoxDecoration(
color: const Color(0xFF063D36)
.withValues(alpha: 0.22),
borderRadius: BorderRadius.circular(32.r),
boxShadow: [
BoxShadow(
color: const Color(0xFF39FF88)
.withValues(alpha: 0.18),
blurRadius: 12.w,
spreadRadius: 1.w,
),
],
)
: null,
child: ColorFiltered(
colorFilter: isActive
? const ColorFilter.mode(
Color(0xFF8DFFCB),
BlendMode.srcATop,
)
: const ColorFilter.mode(
Colors.transparent,
BlendMode.dst,
),
child: Image.asset( child: Image.asset(
item.iconPath, item.iconPath,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
),
);
},
),
], ],
), ),
......
...@@ -435,6 +435,9 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -435,6 +435,9 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
try { try {
await _bluetoothManager.connectToDevice(device); await _bluetoothManager.connectToDevice(device);
if (!_bluetoothManager.isConnected) {
throw StateError('蓝牙连接未就绪');
}
await _storageService.saveBoundBluetoothDevice( await _storageService.saveBoundBluetoothDevice(
deviceId: device.remoteId, deviceId: device.remoteId,
deviceName: device.name, deviceName: device.name,
...@@ -530,7 +533,11 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -530,7 +533,11 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
'deviceId=$deviceId isConnected=${_bluetoothManager.isConnected}', 'deviceId=$deviceId isConnected=${_bluetoothManager.isConnected}',
); );
if (!isClosed) { if (!isClosed) {
emit(state.copyWith(bluetoothMessage: '蓝牙设备已自动连接')); emit(state.copyWith(
bluetoothMessage: _bluetoothManager.isConnected
? '蓝牙设备已自动连接'
: '蓝牙自动连接未就绪,等待重试',
));
} }
} catch (e) { } catch (e) {
debugPrint( debugPrint(
...@@ -893,9 +900,6 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -893,9 +900,6 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_mcuReportChangedSub?.cancel(); _mcuReportChangedSub?.cancel();
_webrtcService.dispose(); _webrtcService.dispose();
_p2pVideoService.dispose(); _p2pVideoService.dispose();
return Future.wait([ return _disposeMqttClient().then((_) => super.close());
_disposeMqttClient(),
_bluetoothManager.release(),
]).then((_) => super.close());
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment