Commit 6951e93
Changed files (5)
app
lib
components
ble_input
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