From 75afc6bb3a5b696b22d400dedd4762f38c25b98e Mon Sep 17 00:00:00 2001 From: Dennis Moschina <45356478+DennisMoschina@users.noreply.github.com> Date: Fri, 17 Apr 2026 11:25:22 +0200 Subject: [PATCH 1/4] fix(v2_sensor_scheme_reader): improve sensor scheme notification handling and add logging --- .../v2_sensor_scheme_reader.dart | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/src/utils/sensor_scheme_parser/v2_sensor_scheme_reader.dart b/lib/src/utils/sensor_scheme_parser/v2_sensor_scheme_reader.dart index 33637bde..78f8b9be 100644 --- a/lib/src/utils/sensor_scheme_parser/v2_sensor_scheme_reader.dart +++ b/lib/src/utils/sensor_scheme_parser/v2_sensor_scheme_reader.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; +import 'package:open_earable_flutter/open_earable_flutter.dart' show logger; import 'package:open_earable_flutter/src/constants.dart'; import '../../managers/ble_gatt_manager.dart'; @@ -48,13 +49,18 @@ class V2SensorSchemeReader extends SensorSchemeReader { } // Listen to the notification of the characteristic - Stream stream = _bleManager.subscribe( + final Stream> stream = _bleManager.subscribe( deviceId: _deviceId, serviceId: parseInfoServiceUuid, characteristicId: sensorSchemeCharacteristicUuid, ); - // Request sensor value + final Future> responseFuture = stream + .cast>() + .first + .timeout(const Duration(seconds: 5)); + + // Request sensor value only after the listener/future is set up await _bleManager.write( deviceId: _deviceId, serviceId: parseInfoServiceUuid, @@ -62,22 +68,22 @@ class V2SensorSchemeReader extends SensorSchemeReader { byteData: [sensorId], ); - // Wait for the notification try { - await for (List value in stream.timeout(const Duration(seconds: 5))) { - SensorScheme scheme = _parseSensorScheme(value); + final value = await responseFuture; + logger.d("Received notification for sensor scheme of sensor $sensorId: $value"); + + final scheme = _parseSensorScheme(value); if (scheme.sensorId != sensorId) { - throw Exception("Sensor id mismatch. Expected: $sensorId, got: ${scheme.sensorId}"); + throw Exception( + "Sensor id mismatch. Expected: $sensorId, got: ${scheme.sensorId}", + ); } _sensorSchemes[sensorId] = scheme; return scheme; - } } on TimeoutException catch (e) { - throw Exception("Timeout while waiting for sensor scheme: $e"); + throw TimeoutException("Timeout while waiting for sensor scheme: $e"); } - - throw Exception("Unknown error while waiting for sensor scheme."); } @override From 6e4a8c697c7667990bafd9415a947adefee97a08 Mon Sep 17 00:00:00 2001 From: Dennis Moschina <45356478+DennisMoschina@users.noreply.github.com> Date: Fri, 17 Apr 2026 11:25:50 +0200 Subject: [PATCH 2/4] fix(ble_manager): refactor stream controller handling for improved memory management --- lib/src/managers/ble_manager.dart | 47 +++++++++++++------------------ 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/lib/src/managers/ble_manager.dart b/lib/src/managers/ble_manager.dart index b9032e9d..0698ec8a 100644 --- a/lib/src/managers/ble_manager.dart +++ b/lib/src/managers/ble_manager.dart @@ -14,7 +14,7 @@ class BleManager extends BleGattManager { int _mtu = _desiredMtu; // Largest Byte package sent is 42 bytes for IMU int get mtu => _mtu; - final Map>>> _streamControllers = {}; + final Map>> _streamControllers = {}; /// A stream of discovered devices during scanning. StreamController? _scanStreamController; @@ -48,13 +48,8 @@ class BleManager extends BleGattManager { .toList(); for (final key in keys) { - final controllers = _streamControllers.remove(key); - if (controllers == null) { - continue; - } - for (final controller in controllers) { - controller.close(); - } + logger.d("Closing stream for $key due to device disconnection"); + _streamControllers.remove(key)?.close(); } } @@ -89,9 +84,11 @@ class BleManager extends BleGattManager { if (!_streamControllers.containsKey(streamIdentifier)) { return; } - for (var e in _streamControllers[streamIdentifier]!) { - e.add(value); + if (_streamControllers[streamIdentifier] == null) { + logger.w("Stream controller for $streamIdentifier is null"); + return; } + _streamControllers[streamIdentifier]!.add(value); }; } @@ -308,30 +305,28 @@ class BleManager extends BleGattManager { required String serviceId, required String characteristicId, }) { - final streamController = StreamController>(); + logger.d("Subscribing to $deviceId, service $serviceId, characteristic $characteristicId"); String streamIdentifier = _getCharacteristicKey(deviceId, characteristicId); + StreamController>? streamController = _streamControllers[streamIdentifier]; + streamController ??= StreamController>.broadcast(); if (!_streamControllers.containsKey(streamIdentifier)) { UniversalBle.subscribeNotifications( deviceId, serviceId, characteristicId, ); - _streamControllers[streamIdentifier] = [streamController]; - } else { - _streamControllers[streamIdentifier]!.add(streamController); + _streamControllers[streamIdentifier] = streamController; } streamController.onCancel = () { if (_streamControllers.containsKey(streamIdentifier)) { - _streamControllers[streamIdentifier]!.remove(streamController); - if (_streamControllers[streamIdentifier]!.isEmpty) { - UniversalBle.unsubscribe( - deviceId, - serviceId, - characteristicId, - ); - _streamControllers.remove(streamIdentifier); - } + _streamControllers.remove(streamIdentifier)?.close(); + UniversalBle.unsubscribe( + deviceId, + serviceId, + characteristicId, + ); + _streamControllers.remove(streamIdentifier); } }; @@ -373,10 +368,8 @@ class BleManager extends BleGattManager { UniversalBle.onScanResult = (_) {}; _scanStreamController?.close(); - for (var list in _streamControllers.values) { - for (var e in list) { - e.close(); - } + for (var controller in _streamControllers.values) { + controller.close(); } } } From 754b8babc07e4876a41d65cf3b912a1c4ad49b41 Mon Sep 17 00:00:00 2001 From: Dennis Moschina <45356478+DennisMoschina@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:23:39 +0200 Subject: [PATCH 3/4] fix(ble_manager): refactor getSystemDevices method and improve stream handling --- lib/src/managers/ble_manager.dart | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/src/managers/ble_manager.dart b/lib/src/managers/ble_manager.dart index 0698ec8a..de50c86b 100644 --- a/lib/src/managers/ble_manager.dart +++ b/lib/src/managers/ble_manager.dart @@ -43,9 +43,8 @@ class BleManager extends BleGattManager { void _closeAndRemoveStreamsForDevice(String deviceId) { final prefix = "$deviceId||"; - final keys = _streamControllers.keys - .where((key) => key.startsWith(prefix)) - .toList(); + final keys = + _streamControllers.keys.where((key) => key.startsWith(prefix)).toList(); for (final key in keys) { logger.d("Closing stream for $key due to device disconnection"); @@ -174,8 +173,11 @@ class BleManager extends BleGattManager { /// Throws an exception if called on web. /// If no devices are found, returns an empty list. /// If the platform is not web, it uses `UniversalBle.getSystemDevices`. - Future> getSystemDevices() async { - if (!await checkAndRequestPermissions()) { + Future> getSystemDevices({ + bool checkAndRequestPermissions = true, + }) async { + if (checkAndRequestPermissions && + !await BleManager.checkAndRequestPermissions()) { throw Exception("Permissions not granted"); } if (kIsWeb) { @@ -241,8 +243,6 @@ class BleManager extends BleGattManager { required String deviceId, required String serviceId, }) async { - - if (!isConnected(deviceId)) { throw Exception("Device is not connected"); } @@ -270,7 +270,8 @@ class BleManager extends BleGattManager { for (final service in services) { if (service.uuid.toLowerCase() == serviceId.toLowerCase()) { for (final characteristic in service.characteristics) { - if (characteristic.uuid.toLowerCase() == characteristicId.toLowerCase()) { + if (characteristic.uuid.toLowerCase() == + characteristicId.toLowerCase()) { return true; } } @@ -305,9 +306,15 @@ class BleManager extends BleGattManager { required String serviceId, required String characteristicId, }) { - logger.d("Subscribing to $deviceId, service $serviceId, characteristic $characteristicId"); - String streamIdentifier = _getCharacteristicKey(deviceId, characteristicId); - StreamController>? streamController = _streamControllers[streamIdentifier]; + logger.d( + "Subscribing to $deviceId, service $serviceId, characteristic $characteristicId", + ); + String streamIdentifier = _getCharacteristicKey( + deviceId, + characteristicId, + ); + StreamController>? streamController = + _streamControllers[streamIdentifier]; streamController ??= StreamController>.broadcast(); if (!_streamControllers.containsKey(streamIdentifier)) { UniversalBle.subscribeNotifications( From 8f66fcf1f2fc3a6797e60ee1954226ef5d08418d Mon Sep 17 00:00:00 2001 From: Dennis Moschina <45356478+DennisMoschina@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:24:31 +0200 Subject: [PATCH 4/4] feat(wearable_manager): add getSystemDevices method for retrieving known Bluetooth devices --- lib/open_earable_flutter.dart | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/open_earable_flutter.dart b/lib/open_earable_flutter.dart index b0baaaf1..c3262467 100644 --- a/lib/open_earable_flutter.dart +++ b/lib/open_earable_flutter.dart @@ -141,6 +141,18 @@ class WearableManager { return await BleManager.checkPermissions(); } + /// Retrieves Bluetooth devices already known to the operating system. + /// + /// When [checkAndRequestPermissions] is `true`, the manager first requests + /// any missing runtime permissions required for system-device discovery. + Future> getSystemDevices({ + bool checkAndRequestPermissions = true, + }) { + return _bleManager.getSystemDevices( + checkAndRequestPermissions: checkAndRequestPermissions, + ); + } + /// Adds a wearable factory to the manager. /// Wearable factories are used to create wearable instances based on the connected devices. /// This allows the manager to support multiple types of wearables. @@ -169,7 +181,8 @@ class WearableManager { } /// Returns an unmodifiable list of the currently registered wearable factories. - List get wearableFactories => List.unmodifiable(_wearableFactories); + List get wearableFactories => + List.unmodifiable(_wearableFactories); /// Starts scanning for BLE devices. /// If `checkAndRequestPermissions` is true, it will check and request the necessary @@ -292,10 +305,10 @@ class WearableManager { ConnectionFailedException _ => 'Failed to connect to device "$normalizedDeviceName". Please try again.', _ => _normalizeDeviceNameInMessage( - message: e.toString(), - rawDeviceName: deviceName, - normalizedDeviceName: normalizedDeviceName, - ), + message: e.toString(), + rawDeviceName: deviceName, + normalizedDeviceName: normalizedDeviceName, + ), }; }