main
1import 'dart:async';
2
3import 'package:blood_pressure_app/features/bluetooth/backend/bluetooth_connection.dart';
4import 'package:blood_pressure_app/features/bluetooth/backend/bluetooth_device.dart';
5import 'package:blood_pressure_app/features/bluetooth/backend/flutter_blue_plus/fbp_manager.dart';
6import 'package:blood_pressure_app/features/bluetooth/backend/flutter_blue_plus/fbp_service.dart';
7import 'package:flutter/foundation.dart';
8import 'package:flutter_blue_plus/flutter_blue_plus.dart' as fbp;
9
10/// BluetoothDevice implementation for the 'flutter_blue_plus' package
11final class FlutterBluePlusDevice
12 extends BluetoothDevice<
13 FlutterBluePlusManager,
14 FlutterBluePlusService,
15 FlutterBluePlusCharacteristic,
16 fbp.ScanResult
17 >
18 with CharacteristicValueListener<
19 FlutterBluePlusManager,
20 FlutterBluePlusService,
21 FlutterBluePlusCharacteristic,
22 fbp.ScanResult
23 >
24{
25 /// Initialize BluetoothDevice implementation for the 'flutter_blue_plus' package
26 FlutterBluePlusDevice(super.manager, super.source);
27
28 @override
29 String get deviceId => source.device.remoteId.str;
30
31 @override
32 String get name => source.device.platformName;
33
34 @override
35 Stream<BluetoothConnectionState> get connectionStream => source.device.connectionState
36 .map((fbp.BluetoothConnectionState rawState) => switch (rawState) {
37 fbp.BluetoothConnectionState.connected => BluetoothConnectionState.connected,
38 fbp.BluetoothConnectionState.disconnected => BluetoothConnectionState.disconnected,
39 // code should never reach here
40 fbp.BluetoothConnectionState.connecting || fbp.BluetoothConnectionState.disconnecting
41 => throw ErrorDescription('Unsupported connection state: $rawState'),
42 });
43
44 @override
45 Future<void> backendConnect() => source.device.connect();
46
47 @override
48 Future<void> backendDisconnect() => source.device.disconnect();
49
50 @override
51 Future<void> dispose() => disposeCharacteristics();
52
53 @override
54 Future<List<FlutterBluePlusService>?> discoverServices() async {
55 if (!isConnected) {
56 logger.finer('Device not connected, cannot discover services');
57 return null;
58 }
59
60 // Query actual services supported by the device. While they must be
61 // rediscovered when a disconnect happens, this object is also recreated.
62 try {
63 final allServices = await source.device.discoverServices();
64 logger.finer('fbp.discoverServices: $allServices');
65
66 return allServices.map(FlutterBluePlusService.fromSource).toList();
67 } catch (e) {
68 logger.shout('Error on service discovery', [source.device, e]);
69 return null;
70 }
71 }
72
73 @override
74 Future<bool> triggerCharacteristicValue(FlutterBluePlusCharacteristic characteristic, [bool state = true]) => characteristic.source.setNotifyValue(state);
75
76 @override
77 Future<bool> getCharacteristicValue(
78 FlutterBluePlusCharacteristic 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 characteristic.source.read();
90 onValue(Uint8List.fromList(data));
91 return true;
92 } catch (err) {
93 logger.severe('getCharacteristicValue(read error)', err);
94 return false;
95 }
96 }
97
98 if (characteristic.canIndicate) { // Listen for characteristic value and trigger the device to send it
99 return listenCharacteristicValue(
100 characteristic,
101 characteristic.source.onValueReceived.map(Uint8List.fromList),
102 onValue
103 );
104 }
105
106 logger.severe("Can't read or indicate characteristic: $characteristic");
107 return false;
108 }
109}