Commit d26297b

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-05-01 09:54:39
implement ble reading cubit
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent 2b8a9d9
app/lib/bluetooth/ble_read_cubit.dart
@@ -0,0 +1,73 @@
+import 'dart:async';
+
+import 'package:blood_pressure_app/bluetooth/flutter_blue_plus_mockable.dart';
+import 'package:blood_pressure_app/bluetooth/logging.dart';
+import 'package:collection/collection.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_blue_plus/flutter_blue_plus.dart';
+
+part 'ble_read_state.dart';
+
+/// Logic for reading a characteristic from a device.
+///
+/// May only be used on devices that are fully connected.
+class BleReadCubit extends Cubit<BleRead> {
+  /// Start reading a characteristic from a device.
+  BleReadCubit(this._flutterBluePlus, this._device)
+    : super(BleReadInProgress()) {
+   unawaited(_startRead());
+  }
+
+  final FlutterBluePlusMockable _flutterBluePlus;
+
+  /// Bluetooth device to connect to.
+  ///
+  /// Must have an active established connection and support the measurement
+  /// characteristic.
+  final BluetoothDevice _device;
+
+  Future<void> _startRead() async {
+    // Query actual services supported by the device. While they must be
+    // rediscovered when a disconnect happens, this object is also recreated.
+    late final List<BluetoothService> allServices;
+    try {
+      allServices = await _device.discoverServices();
+    } catch (e) {
+      emit(BleReadFailure());
+      Log.err('service discovery', [_device, e]);
+    }
+
+
+    // [Guid.str] trims standard parts from the uuid. 0x1810 is the blood
+    // pressure uuid. https://developer.nordicsemi.com/nRF51_SDK/nRF51_SDK_v4.x.x/doc/html/group___u_u_i_d___s_e_r_v_i_c_e_s.html
+    final service = allServices.firstWhereOrNull((s) => s.uuid.str == '1810');
+    if (service == null) {
+      Log.err('unsupported service', [_device, allServices]);
+      emit(BleReadFailure());
+      return;
+    }
+
+    // https://developer.nordicsemi.com/nRF51_SDK/nRF51_SDK_v4.x.x/doc/html/group___u_u_i_d___c_h_a_r_a_c_t_e_r_i_s_t_i_c_s.html#ga95fc99c7a99cf9d991c81027e4866936
+    final allCharacteristics = service.characteristics;
+    final characteristic = allCharacteristics.firstWhereOrNull(
+            (c) => c.uuid.str == '2A35');
+    if (characteristic == null) {
+      emit(BleReadFailure());
+      Log.err('no characteristic', [_device, allServices, allCharacteristics]);
+      return;
+    }
+
+    late final List<int> data;
+    try {
+      data = await characteristic.read();
+    } catch (e) {
+      emit(BleReadFailure());
+      Log.err('read error', [_device, allServices, characteristic, e]);
+    }
+
+    // TODO: decode data before emitting success.
+    emit(BleReadSuccess(data));
+  }
+}
app/lib/bluetooth/ble_read_state.dart
@@ -0,0 +1,20 @@
+part of 'ble_read_cubit.dart';
+
+/// State of reading a characteristic from a BLE device.
+@immutable
+abstract class BleRead {}
+
+/// The reading has been started.
+class BleReadInProgress extends BleRead {}
+
+/// The reading failed unrecoverable for some reason.
+class BleReadFailure extends BleRead {}
+
+/// Data has been successfully read.
+class BleReadSuccess extends BleRead {
+  /// Indicate a successful reading of a ble characteristic.
+  BleReadSuccess(this.data);
+
+  /// Raw binary data received from the device.
+  final List<int> data;
+}
app/lib/bluetooth/device_scan_cubit.dart
@@ -56,8 +56,11 @@ class DeviceScanCubit extends Cubit<DeviceScanState> {
     );
     try {
       await _flutterBluePlus.startScan(
-        withServices: [service],
         // no timeout, the user knows best how long scanning is needed
+        withServices: [service],
+        // Might not find the device (https://pub.dev/packages/flutter_blue_plus#scanning-does-not-find-my-device)
+        // TODO: Make decision on whether to support these devices
+
       );
     } catch (e) {
       _onScanError(e);
app/lib/bluetooth/logging.dart
@@ -0,0 +1,17 @@
+import 'package:dump/dump.dart'; // only used in debug mode
+import 'package:flutter/foundation.dart';
+
+/// Simple class for manually logging in debug builds.
+class Log {
+  /// Log an error with stack trace in debug builds.
+  static void err(String message, [List<Object>? dumps]) {
+    if (kDebugMode) {
+      debugPrint('-----------------------------');
+      debugPrintStack();
+      debugPrint('ERROR $message:');
+      for (final e in dumps ?? []) {
+        dumpJson(e);
+      }
+    }
+  }
+}
\ No newline at end of file
app/lib/components/ble_input/tmp.dart
@@ -0,0 +1,107 @@
+import 'dart:async';
+
+import 'package:flutter_blue_plus/flutter_blue_plus.dart';
+
+class BeforeScanning {
+  late StreamSubscription<BluetoothAdapterState> _adapterStateStateSubscription;
+
+  // Must be: `on`
+  BluetoothAdapterState _adapterState = BluetoothAdapterState.unknown;
+
+  init() {
+    _adapterStateStateSubscription = FlutterBluePlus.adapterState.listen((state) {
+      setState(() {
+        _adapterState = state;
+      });
+    });
+  }
+
+  dispose() {
+    _adapterStateStateSubscription.cancel();
+  }
+
+  enableBluetooth() {
+    try {
+      if (Platform.isAndroid) {
+        await FlutterBluePlus.turnOn();
+      }
+    } catch (e) {
+      Snackbar.show(ABC.a, prettyException("Error Turning On:", e), success: false);
+    }
+  }
+}
+
+class ScanningPhase {
+  List<BluetoothDevice> _systemDevices = [];
+  List<ScanResult> _scanResults = [];
+  bool _isScanning = false;
+  late StreamSubscription<List<ScanResult>> _scanResultsSubscription;
+  late StreamSubscription<bool> _isScanningSubscription;
+
+  initState() {
+    super.initState();
+
+    _scanResultsSubscription = FlutterBluePlus.scanResults.listen((results) {
+      _scanResults = results;
+    }, onError: (e) {
+      Snackbar.show(ABC.b, prettyException("Scan Error:", e), success: false);
+    });
+
+    _isScanningSubscription = FlutterBluePlus.isScanning.listen((state) {
+      _isScanning = state;
+    });
+  }
+
+  dispose() {
+    _scanResultsSubscription.cancel();
+    _isScanningSubscription.cancel();
+    super.dispose();
+  }
+
+  onScanPressed() async {
+    try {
+      _systemDevices = await FlutterBluePlus.systemDevices;
+    } catch (e) {
+      Snackbar.show(ABC.b, prettyException("System Devices Error:", e), success: false);
+    }
+    try {
+      await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
+    } catch (e) {
+      Snackbar.show(ABC.b, prettyException("Start Scan Error:", e), success: false);
+    }
+  }
+
+  onStopPressed() async {
+    try {
+      FlutterBluePlus.stopScan();
+    } catch (e) {
+      Snackbar.show(ABC.b, prettyException("Stop Scan Error:", e), success: false);
+    }
+  }
+}
+
+class DeviceInfo {
+
+  userInfo() {
+    widget.result._device.platformName.isNotEmpty
+        ? widget.result._device.platformName
+        : widget.result._device.remoteId.str
+  }
+
+  bool get isConnectable => widget.result.advertisementData.connectable;
+
+  connect() {
+    _device.connectAndUpdateStream().catchError((e) {
+      Snackbar.show(ABC.c, prettyException("Connect Error:", e), success: false);
+    });
+  }
+}
+
+class DeviceConnection {
+  late StreamSubscription<BluetoothConnectionState> _connectionStateSubscription;
+  late StreamSubscription<bool> _isConnectingSubscription;
+  late StreamSubscription<bool> _isDisconnectingSubscription;
+  late StreamSubscription<int> _mtuSubscription;
+
+  // https://github.com/boskokg/flutter_blue_plus/blob/master/example/lib/screens/device_screen.dart
+}
\ No newline at end of file
app/pubspec.lock
@@ -217,6 +217,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.3.6"
+  dump:
+    dependency: "direct dev"
+    description:
+      name: dump
+      sha256: "3f3d4eb634539b6bdcabe1ac9c42dd96952c502785ae130b482d9c2879f78c98"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.1.6"
   equatable:
     dependency: transitive
     description:
@@ -424,10 +432,10 @@ packages:
     dependency: "direct main"
     description:
       name: intl
-      sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
+      sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
       url: "https://pub.dev"
     source: hosted
-    version: "0.18.1"
+    version: "0.19.0"
   io:
     dependency: transitive
     description:
@@ -460,6 +468,30 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "4.8.1"
+  leak_tracker:
+    dependency: transitive
+    description:
+      name: leak_tracker
+      sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
+      url: "https://pub.dev"
+    source: hosted
+    version: "10.0.4"
+  leak_tracker_flutter_testing:
+    dependency: transitive
+    description:
+      name: leak_tracker_flutter_testing
+      sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.3"
+  leak_tracker_testing:
+    dependency: transitive
+    description:
+      name: leak_tracker_testing
+      sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.1"
   lints:
     dependency: transitive
     description:
@@ -488,26 +520,26 @@ packages:
     dependency: transitive
     description:
       name: matcher
-      sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
+      sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
       url: "https://pub.dev"
     source: hosted
-    version: "0.12.16"
+    version: "0.12.16+1"
   material_color_utilities:
     dependency: transitive
     description:
       name: material_color_utilities
-      sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
+      sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
       url: "https://pub.dev"
     source: hosted
-    version: "0.5.0"
+    version: "0.8.0"
   meta:
     dependency: transitive
     description:
       name: meta
-      sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
+      sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
       url: "https://pub.dev"
     source: hosted
-    version: "1.10.0"
+    version: "1.12.0"
   mime:
     dependency: transitive
     description:
@@ -560,10 +592,10 @@ packages:
     dependency: "direct main"
     description:
       name: path
-      sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
+      sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
       url: "https://pub.dev"
     source: hosted
-    version: "1.8.3"
+    version: "1.9.0"
   path_parsing:
     dependency: transitive
     description:
@@ -917,10 +949,10 @@ packages:
     dependency: transitive
     description:
       name: test_api
-      sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
+      sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
       url: "https://pub.dev"
     source: hosted
-    version: "0.6.1"
+    version: "0.7.0"
   timing:
     dependency: transitive
     description:
@@ -1017,6 +1049,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.4"
+  vm_service:
+    dependency: transitive
+    description:
+      name: vm_service
+      sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
+      url: "https://pub.dev"
+    source: hosted
+    version: "14.2.1"
   watcher:
     dependency: transitive
     description:
@@ -1074,5 +1114,5 @@ packages:
     source: hosted
     version: "3.1.2"
 sdks:
-  dart: ">=3.2.0 <4.0.0"
-  flutter: ">=3.16.0"
+  dart: ">=3.3.0 <4.0.0"
+  flutter: ">=3.18.0-18.0.pre.54"
app/pubspec.yaml
@@ -48,6 +48,7 @@ dev_dependencies:
   sqflite_common_ffi: ^2.3.0
   translations_cleaner: ^0.0.5
   build_runner: ^2.4.9
+  dump: ^0.1.6
 
 flutter:
   uses-material-design: true