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}