Commit 53839dd5 authored by 张宏's avatar 张宏
parents 08cc8142 65c9e3b0
......@@ -88,10 +88,12 @@ class BleBluetoothManager<T> {
String? _connectedDeviceId;
QualifiedCharacteristic? _notifyCharacteristic;
QualifiedCharacteristic? _writeCharacteristic;
bool _writeCharacteristicOnlyWithoutResponse = false;
StreamSubscription<DiscoveredDevice>? _scanSubscription;
StreamSubscription<ConnectionStateUpdate>? _connectionSubscription;
StreamSubscription<List<int>>? _notifySubscription;
Timer? _scanTimer;
Future<void> _writeQueue = Future<void>.value();
final Map<String, BluetoothScanDevice> _scannedDevices = {};
......@@ -137,7 +139,7 @@ class BleBluetoothManager<T> {
final nextStatus = await _ble.statusStream
.where((value) => value != BleStatus.unknown)
.first
.timeout(const Duration(seconds: 2), onTimeout: () => status);
.timeout(const Duration(seconds: 8), onTimeout: () => status);
if (nextStatus != BleStatus.ready) {
debugPrint(
......@@ -258,13 +260,14 @@ class BleBluetoothManager<T> {
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_to_device_id empty_device_id');
_notifyMessage('无效的蓝牙设备地址');
return;
throw ArgumentError('无效的蓝牙设备地址');
}
if (!await ensureBluetoothReady()) {
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_to_device_id bluetooth_not_ready');
return;
_setStatus(BluetoothConnectionStatus.disconnected);
throw StateError('蓝牙未就绪或未授权');
}
await disconnect();
......@@ -306,11 +309,31 @@ class BleBluetoothManager<T> {
_notifyMessage('蓝牙连接成功: $deviceId');
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG state_connected deviceId=$deviceId');
await _enableNotifications(deviceId);
try {
await _discoverServicesAndResolveWriteCharacteristic(deviceId);
final notifyReady = await _enableNotifications(deviceId);
if (notifyReady) {
activateDataSource();
} else {
_notifyMessage('蓝牙已连接,但未找到可订阅的接收特征值');
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG connected_without_notify '
'deviceId=$deviceId',
);
}
if (!completer.isCompleted) {
completer.complete();
}
} catch (error) {
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG connected_setup_error '
'deviceId=$deviceId error=$error',
);
_notifyMessage('蓝牙连接初始化失败: $error');
if (!completer.isCompleted) {
completer.completeError(error);
}
}
break;
case DeviceConnectionState.disconnecting:
_setStatus(BluetoothConnectionStatus.disconnected);
......@@ -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) {
_notifyMessage('发送数据为空');
_sendResultController.add(false);
......@@ -377,16 +414,19 @@ class BleBluetoothManager<T> {
}
final hex = bytesToHex(data);
final effectiveWithoutResponse =
withoutResponse || _writeCharacteristicOnlyWithoutResponse;
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG write_data '
'deviceId=${characteristic.deviceId} '
'service=${characteristic.serviceId} '
'characteristic=${characteristic.characteristicId} '
'withoutResponse=$withoutResponse '
'withoutResponse=$effectiveWithoutResponse '
'data=$data '
'hex=$hex',
);
try {
if (withoutResponse) {
if (effectiveWithoutResponse) {
await _ble.writeCharacteristicWithoutResponse(
characteristic,
value: data,
......@@ -401,6 +441,8 @@ class BleBluetoothManager<T> {
Log.warn('🔥🔥🔥 BLE_WRITE_DEBUG write_error hex=$hex error=$error');
_notifyMessage('蓝牙数据发送失败: $error');
_sendResultController.add(false);
} finally {
await Future<void>.delayed(const Duration(milliseconds: 120));
}
}
......@@ -435,20 +477,23 @@ class BleBluetoothManager<T> {
await _sendResultController.close();
}
Future<void> _enableNotifications(String deviceId) async {
Future<bool> _enableNotifications(String deviceId) async {
final characteristic = _notifyCharacteristic;
if (characteristic == null) {
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG notify_characteristic_null deviceId=$deviceId');
throw StateError('接收特征值为空');
return false;
}
try {
await _notifySubscription?.cancel();
debugPrint(
'🔥🔥🔥 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) {
if (!isActive || bytes.isEmpty) {
return;
......@@ -477,11 +522,126 @@ class BleBluetoothManager<T> {
);
debugPrint(
'🔥🔥🔥 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() {
_notifyCharacteristic = null;
_writeCharacteristic = null;
_writeCharacteristicOnlyWithoutResponse = false;
}
void _setStatus(BluetoothConnectionStatus status) {
......@@ -524,6 +684,10 @@ class BleBluetoothManager<T> {
return prefixes.any(value.startsWith);
}
static bool _isSameUuid(Uuid left, Uuid right) {
return left.toString().toLowerCase() == right.toString().toLowerCase();
}
static String _normalizeRemoteIdForSort(String remoteId) {
return remoteId.replaceAll(RegExp(r'[^0-9a-zA-Z]'), '').toUpperCase();
}
......
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.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);
......@@ -77,15 +78,57 @@ class MonitoringBottomMenu extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 图标
Container(
StreamBuilder<BluetoothConnectionStatus>(
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,
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(
item.iconPath,
fit: BoxFit.contain,
),
),
),
);
},
),
],
),
......
......@@ -522,6 +522,9 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
try {
await _bluetoothManager.connectToDevice(device);
if (!_bluetoothManager.isConnected) {
throw StateError('蓝牙连接未就绪');
}
await _storageService.saveBoundBluetoothDevice(
deviceId: device.remoteId,
deviceName: device.name,
......@@ -620,7 +623,11 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
'deviceId=$deviceId isConnected=${_bluetoothManager.isConnected}',
);
if (!isClosed) {
emit(state.copyWith(bluetoothMessage: '蓝牙设备已自动连接'));
emit(state.copyWith(
bluetoothMessage: _bluetoothManager.isConnected
? '蓝牙设备已自动连接'
: '蓝牙自动连接未就绪,等待重试',
));
}
} catch (e) {
debugPrint(
......@@ -1074,9 +1081,6 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_mcuReportChangedSub?.cancel();
_webrtcService.dispose();
_p2pVideoService.dispose();
return Future.wait([
_disposeMqttClient(),
_bluetoothManager.release(),
]).then((_) => super.close());
return _disposeMqttClient().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