main
1import 'dart:async';
2import 'dart:typed_data';
3
4import 'package:blood_pressure_app/features/bluetooth/backend/bluetooth_connection.dart';
5import 'package:blood_pressure_app/features/bluetooth/backend/bluetooth_device.dart';
6import 'package:blood_pressure_app/features/bluetooth/backend/bluetooth_low_energy/ble_manager.dart';
7import 'package:blood_pressure_app/features/bluetooth/backend/bluetooth_low_energy/ble_service.dart';
8import 'package:bluetooth_low_energy/bluetooth_low_energy.dart' show CentralManager, ConnectionState, DiscoveredEventArgs, PeripheralConnectionStateChangedEventArgs;
9
10/// BluetoothDevice implementation for the 'bluetooth_low_energy' package
11final class BluetoothLowEnergyDevice
12 extends BluetoothDevice<
13 BluetoothLowEnergyManager,
14 BluetoothLowEnergyService,
15 BluetoothLowEnergyCharacteristic,
16 DiscoveredEventArgs
17 > with CharacteristicValueListener<
18 BluetoothLowEnergyManager,
19 BluetoothLowEnergyService,
20 BluetoothLowEnergyCharacteristic,
21 DiscoveredEventArgs
22 >
23{
24 /// Init BluetoothDevice implementation for the 'bluetooth_low_energy' package
25 BluetoothLowEnergyDevice(super.manager, super.source);
26
27 @override
28 String get deviceId => source.peripheral.uuid.toString();
29
30 @override
31 String get name => source.advertisement.name ?? deviceId;
32
33 CentralManager get _cm => manager.backend;
34
35 @override
36 Stream<BluetoothConnectionState> get connectionStream => _cm.connectionStateChanged
37 .map((PeripheralConnectionStateChangedEventArgs rawState) => switch (rawState.state) {
38 ConnectionState.connected => BluetoothConnectionState.connected,
39 ConnectionState.disconnected => BluetoothConnectionState.disconnected,
40 });
41
42 @override
43 Future<void> backendConnect() => _cm.connect(source.peripheral);
44
45 @override
46 Future<void> backendDisconnect() => _cm.disconnect(source.peripheral);
47
48 @override
49 Future<void> dispose() => disposeCharacteristics();
50
51 @override
52 Future<List<BluetoothLowEnergyService>?> discoverServices() async {
53 if (!isConnected) {
54 logger.finer('discoverServices: device not connect. Call device.connect() first');
55 return null;
56 }
57
58 // Query actual services supported by the device. While they must be
59 // rediscovered when a disconnect happens, this object is also recreated.
60 try {
61 final rawServices = await _cm.discoverGATT(source.peripheral);
62 logger.finer('discoverServices: $rawServices');
63 return rawServices.map(BluetoothLowEnergyService.fromSource).toList();
64 } catch (e) {
65 logger.shout('discoverServices: error:', [source.peripheral, e]);
66 return null;
67 }
68 }
69
70 @override
71 Future<bool> triggerCharacteristicValue(BluetoothLowEnergyCharacteristic characteristic, [bool state = true]) async {
72 await _cm.setCharacteristicNotifyState(source.peripheral, characteristic.source, state: state);
73 return true;
74 }
75
76 @override
77 Future<bool> getCharacteristicValue(
78 BluetoothLowEnergyCharacteristic characteristic,
79 void Function(Uint8List value, [void Function(bool success)? complete]) onValue,
80 ) async {
81 if (!isConnected) {
82 assert(false, 'getCharacteristicValue: device not connected. Call device.connect() first');
83 logger.finer('getCharacteristicValue: device not connected.');
84 return false;
85 }
86
87 if (characteristic.canRead) { // Read characteristic value if supported
88 try {
89 final data = await _cm.readCharacteristic(
90 source.peripheral,
91 characteristic.source,
92 );
93
94 onValue(data);
95 return true;
96 } catch (err) {
97 logger.warning('getCharacteristicValue: ble readCharacteristic failed', err);
98 return false;
99 }
100 }
101
102 else if (characteristic.canIndicate) { // Listen for characteristic value and trigger the device to send it
103 return listenCharacteristicValue(
104 characteristic,
105 _cm.characteristicNotified.map((eventArgs) {
106 if (characteristic.source != eventArgs.characteristic) {
107 /// characteristicNotified is a generic stream which ble does not
108 /// pre-filter for just the current requested characteristic
109 logger.finer(' data is for a different characteristic');
110 logger.finest(' ${eventArgs.characteristic.uuid} == ${characteristic.source.uuid} => ${eventArgs.characteristic == characteristic.source}');
111 logger.finest(' ${eventArgs.characteristic} == ${characteristic.source} => ${eventArgs.characteristic == characteristic.source}');
112 return null;
113 }
114
115 return eventArgs.value;
116 }),
117 onValue
118 );
119 }
120
121 logger.severe("Can't read or indicate characteristic: $characteristic");
122 return false;
123 }
124}