diff --git a/CHANGELOG.md b/CHANGELOG.md index 00d95288..d4b67d7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.9 + +* fixed permissions on web, where web was not able to access the devices BLE services + ## 2.3.8 * updated dependencies to latest versions diff --git a/doc/ADD_CUSTOM_WEARABLE.md b/doc/ADD_CUSTOM_WEARABLE.md index e161d886..30a7eb0b 100644 --- a/doc/ADD_CUSTOM_WEARABLE.md +++ b/doc/ADD_CUSTOM_WEARABLE.md @@ -20,10 +20,20 @@ class MyCustomWearable extends Wearable { ## 2. Implement a Custom Wearable Factory -Create a factory that determines when your custom wearable should be used. This factory is responsible for recognizing a device and constructing the corresponding wearable object. If you need to perform ble gatt operations, you can use the `BleGattManager` in `bleManager` of the `WearableFactory` class. The `BleGattManager` provides methods for interacting with BLE devices, such as reading and writing characteristics. It is provided by the `WearableManager` and should not be set manually. +Create a factory that determines when your custom wearable should be used. This factory is responsible for recognizing a device and constructing the corresponding wearable object. If you need to perform BLE GATT operations, you can use the `BleGattManager` in `bleManager` of the `WearableFactory` class. The `BleGattManager` provides methods for interacting with BLE devices, such as reading and writing characteristics. It is provided by the `WearableManager` and should not be set manually. + +If your wearable uses BLE services, declare them in `usedServiceUuids`. Web Bluetooth requires all services to be requested before a device is selected, so the `WearableManager` collects this list from all registered factories when scanning starts. ```dart class MyCustomWearableFactory extends WearableFactory { + static const String _customServiceUuid = + "00000000-0000-1000-8000-000000000000"; + + @override + Set get usedServiceUuids => const { + _customServiceUuid, + }; + @override Future matches(DiscoveredDevice device, List services) async { // Define logic to check if the device matches your custom wearable @@ -45,6 +55,8 @@ class MyCustomWearableFactory extends WearableFactory { } ``` +Include every service your factory may need while matching, creating, or using the wearable. This includes services used by capabilities that are registered during `createFromDevice`, because those capabilities are still created after Web Bluetooth has already requested access. + --- ## 3. Register the Custom Factory diff --git a/example/pubspec.lock b/example/pubspec.lock index 30301d6c..eb7392d2 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -414,7 +414,7 @@ packages: path: ".." relative: true source: path - version: "2.3.7" + version: "2.3.9" package_config: dependency: transitive description: diff --git a/lib/open_earable_flutter.dart b/lib/open_earable_flutter.dart index c3262467..da52dc0b 100644 --- a/lib/open_earable_flutter.dart +++ b/lib/open_earable_flutter.dart @@ -200,6 +200,9 @@ class WearableManager { }) { return _bleManager.startScan( checkAndRequestPermissions: checkAndRequestPermissions, + webOptionalServiceUuids: { + for (final factory in _wearableFactories) ...factory.usedServiceUuids, + }, ); } diff --git a/lib/src/managers/ble_manager.dart b/lib/src/managers/ble_manager.dart index a8a73f16..2f6b54e4 100644 --- a/lib/src/managers/ble_manager.dart +++ b/lib/src/managers/ble_manager.dart @@ -126,6 +126,7 @@ class BleManager extends BleGattManager { /// Initiates the BLE device scan to discover nearby Bluetooth devices. Future startScan({ bool checkAndRequestPermissions = true, + Set webOptionalServiceUuids = const {}, }) async { bool? permGranted; @@ -164,7 +165,19 @@ class BleManager extends BleGattManager { _scanStreamController?.add(device); } } - await UniversalBle.startScan(); + await UniversalBle.startScan( + scanFilter: ScanFilter( + withNamePrefix: ["OpenEarable", "OpenRing", "Cosinuss", "eSense"], + ), + platformConfig: kIsWeb + ? PlatformConfig( + web: WebOptions( + optionalServices: + webOptionalServiceUuids.toList(growable: false), + ), + ) + : null, + ); } _firstScan = false; } diff --git a/lib/src/models/devices/cosinuss_one_factory.dart b/lib/src/models/devices/cosinuss_one_factory.dart index 5eab8e2f..68d47397 100644 --- a/lib/src/models/devices/cosinuss_one_factory.dart +++ b/lib/src/models/devices/cosinuss_one_factory.dart @@ -8,28 +8,44 @@ class CosinussOneFactory extends WearableFactory { static const String _name = "earconnect"; @override - Future matches(DiscoveredDevice device, List services) async { + Set get usedServiceUuids => const { + CosinussOne.ppgAndAccServiceUuid, + CosinussOne.temperatureServiceUuid, + CosinussOne.heartRateServiceUuid, + CosinussOne.batteryServiceUuid, + }; + + @override + Future matches( + DiscoveredDevice device, + List services, + ) async { return device.name == _name; } @override - Future createFromDevice(DiscoveredDevice device, { Set options = const {} }) async { + Future createFromDevice( + DiscoveredDevice device, { + Set options = const {}, + }) async { if (bleManager == null) { - throw Exception("bleManager needs to be set before using the factory"); + throw StateError("bleManager needs to be set before using the factory"); } if (disconnectNotifier == null) { - throw Exception("disconnectNotifier needs to be set before using the factory"); + throw StateError( + "disconnectNotifier needs to be set before using the factory", + ); } if (device.name != _name) { - throw Exception("device is not a cosinuss one"); + throw ArgumentError.value(device.name, 'device.name', 'Expected $_name'); } return CosinussOne( - name: device.name, - disconnectNotifier: disconnectNotifier!, - bleManager: bleManager!, - discoveredDevice: device, - ); + name: device.name, + disconnectNotifier: disconnectNotifier!, + bleManager: bleManager!, + discoveredDevice: device, + ); } } diff --git a/lib/src/models/devices/esense_factory.dart b/lib/src/models/devices/esense_factory.dart index 0e948891..1f798ad2 100644 --- a/lib/src/models/devices/esense_factory.dart +++ b/lib/src/models/devices/esense_factory.dart @@ -15,9 +15,15 @@ import 'wearable.dart'; class EsenseFactory extends WearableFactory { @override - Future createFromDevice(DiscoveredDevice device, - {Set options = const {},}) async { + Set get usedServiceUuids => const { + esenseServiceUuid, + }; + @override + Future createFromDevice( + DiscoveredDevice device, { + Set options = const {}, + }) async { EsenseSensorHandler sensorHandler = EsenseSensorHandler( bleGattManager: bleManager!, discoveredDevice: device, @@ -29,18 +35,25 @@ class EsenseFactory extends WearableFactory { EsenseSensorConfigurationValue(frequencyHz: 50.0), EsenseSensorConfigurationValue(frequencyHz: 100.0), EsenseSensorConfigurationValue(frequencyHz: 200.0), - ].expand((v) => [v, v.copyWith(options: {StreamSensorConfigOption()})]).toList(); - + ] + .expand( + (v) => [ + v, + v.copyWith(options: {StreamSensorConfigOption()}), + ], + ) + .toList(); + final imuConfig = EsenseSensorConfiguration( - name: "6-axis IMU", - values: imuConfigValues, - sensorCommand: 0x53, - sensorHandler: sensorHandler, - availableOptions: { - StreamSensorConfigOption(), - }, - offValue: imuConfigValues.firstWhere((v) => v.options.isEmpty), - ); + name: "6-axis IMU", + values: imuConfigValues, + sensorCommand: 0x53, + sensorHandler: sensorHandler, + availableOptions: { + StreamSensorConfigOption(), + }, + offValue: imuConfigValues.firstWhere((v) => v.options.isEmpty), + ); Esense esense = Esense( name: device.name, @@ -71,12 +84,15 @@ class EsenseFactory extends WearableFactory { ), ], ); - + return esense; } @override - Future matches(DiscoveredDevice device, List services) async { + Future matches( + DiscoveredDevice device, + List services, + ) async { return RegExp(r'^eSense-\d{4}$').hasMatch(device.name); } } @@ -127,7 +143,9 @@ class EsenseSensor extends Sensor { } else if (entry.value is double) { values.add(entry.value as double); } else { - throw Exception("Unsupported sensor value type: ${entry.value.runtimeType}"); + throw UnsupportedError( + "Unsupported sensor value type: ${entry.value.runtimeType}", + ); } } diff --git a/lib/src/models/devices/open_earable_factory.dart b/lib/src/models/devices/open_earable_factory.dart index 11dadebd..a15c0c70 100644 --- a/lib/src/models/devices/open_earable_factory.dart +++ b/lib/src/models/devices/open_earable_factory.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:open_earable_flutter/src/managers/sensor_handler.dart'; import 'package:open_earable_flutter/src/models/wearable_factory.dart'; import 'package:open_earable_flutter/src/utils/sensor_scheme_parser/sensor_scheme_reader.dart'; @@ -33,6 +34,14 @@ class OpenEarableFactory extends WearableFactory { final _v1Regex = RegExp(r'^1\.\d+\.\d+$'); final _v2Regex = RegExp(r'^2\.\d+\.\d+$'); + @override + Set get usedServiceUuids => { + ...OpenEarableV1.serviceUuids, + ...OpenEarableV2.serviceUuids, + mcuMgrSmpServiceUuid, + timeSynchronizationServiceUuid, + }; + @override Future matches( DiscoveredDevice device, @@ -52,7 +61,10 @@ class OpenEarableFactory extends WearableFactory { } @override - Future createFromDevice(DiscoveredDevice device, { Set options = const {} }) async { + Future createFromDevice( + DiscoveredDevice device, { + Set options = const {}, + }) async { if (bleManager == null) { throw Exception("bleManager needs to be set before using the factory"); } @@ -91,7 +103,10 @@ class OpenEarableFactory extends WearableFactory { }, isConnectedViaSystem: options.contains(const ConnectedViaSystem()), ); - if (await bleManager!.hasService(deviceId: device.id, serviceId: timeSynchronizationServiceUuid)) { + if (await bleManager!.hasService( + deviceId: device.id, + serviceId: timeSynchronizationServiceUuid, + )) { wearable.registerCapability( OpenEarableV2TimeSyncImp( bleManager: bleManager!, @@ -151,15 +166,23 @@ class OpenEarableFactory extends WearableFactory { List sensorSchemes = await schemeParser.readSensorSchemes(); + logger.d("Platform: ${kIsWeb ? 'WEB/Chrome' : 'NATIVE'}"); + logger.d( + "Sensor schemes provided by device: ${sensorSchemes.map((s) => 'ID:${s.sensorId}(${s.sensorName})').join(', ')}", + ); + for (SensorScheme scheme in sensorSchemes) { - List sensorConfigurationValues = []; + List sensorConfigurationValues = + []; final features = scheme.options?.features ?? []; final hasStreaming = features.contains(SensorConfigFeatures.streaming); final hasRecording = features.contains(SensorConfigFeatures.recording); - final hasFrequencies = features.contains(SensorConfigFeatures.frequencyDefinition); + final hasFrequencies = + features.contains(SensorConfigFeatures.frequencyDefinition); final frequencies = scheme.options?.frequencies?.frequencies ?? []; - final maxStreamingIndex = scheme.options?.frequencies?.maxStreamingFreqIndex ?? -1; + final maxStreamingIndex = + scheme.options?.frequencies?.maxStreamingFreqIndex ?? -1; //TODO: handle case where no frequencies are defined if (hasFrequencies && frequencies.isNotEmpty) { @@ -211,7 +234,9 @@ class OpenEarableFactory extends WearableFactory { .firstOrNull; if (sensorConfigurationValues.isEmpty) { - logger.w("No configuration values generated for sensor: ${scheme.sensorName}"); + logger.w( + "No configuration values generated for sensor: ${scheme.sensorName}", + ); } final sensorConfiguration = SensorConfigurationOpenEarableV2( @@ -229,16 +254,21 @@ class OpenEarableFactory extends WearableFactory { sensorConfigurations.add(sensorConfiguration); - if (scheme.options?.features.contains(SensorConfigFeatures.streaming) ?? false) { + if (scheme.options?.features.contains(SensorConfigFeatures.streaming) ?? + false) { // Group components by group name final sensorGroups = >{}; for (final component in scheme.components) { - sensorGroups.putIfAbsent(component.groupName, () => []).add(component); + sensorGroups + .putIfAbsent(component.groupName, () => []) + .add(component); } for (final groupName in sensorGroups.keys) { - final axisNames = sensorGroups[groupName]!.map((c) => c.componentName).toList(); - final axisUnits = sensorGroups[groupName]!.map((c) => c.unitName).toList(); + final axisNames = + sensorGroups[groupName]!.map((c) => c.componentName).toList(); + final axisUnits = + sensorGroups[groupName]!.map((c) => c.unitName).toList(); final sensor = _OpenEarableSensorV2( sensorId: scheme.sensorId, diff --git a/lib/src/models/devices/open_earable_v1.dart b/lib/src/models/devices/open_earable_v1.dart index 26cf4069..6366c52e 100644 --- a/lib/src/models/devices/open_earable_v1.dart +++ b/lib/src/models/devices/open_earable_v1.dart @@ -50,6 +50,15 @@ class OpenEarableV1 extends Wearable static const String buttonServiceUuid = "29c10bdc-4773-11ee-be56-0242ac120002"; static const String batteryServiceUuid = "180F"; + static const Set serviceUuids = { + sensorServiceUuid, + parseInfoServiceUuid, + deviceInfoServiceUuid, + ledServiceUuid, + audioPlayerServiceUuid, + buttonServiceUuid, + batteryServiceUuid, + }; final List _sensors; final List _sensorConfigurations; diff --git a/lib/src/models/devices/open_earable_v2.dart b/lib/src/models/devices/open_earable_v2.dart index e2b617d6..9ebb78b2 100644 --- a/lib/src/models/devices/open_earable_v2.dart +++ b/lib/src/models/devices/open_earable_v2.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:typed_data'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:open_earable_flutter/src/constants.dart'; import 'package:open_earable_flutter/src/models/devices/bluetooth_wearable.dart'; import 'package:pub_semver/pub_semver.dart'; @@ -35,7 +36,8 @@ const String _audioModeCharacteristicUuid = const String _buttonServiceUuid = "29c10bdc-4773-11ee-be56-0242ac120002"; const String _buttonCharacteristicUuid = "29c10f38-4773-11ee-be56-0242ac120002"; -const String timeSynchronizationServiceUuid = "2e04cbf7-939d-4be5-823e-271838b75259"; +const String timeSynchronizationServiceUuid = + "2e04cbf7-939d-4be5-823e-271838b75259"; const String _timeSyncTimeMappingCharacteristicUuid = "2e04cbf8-939d-4be5-823e-271838b75259"; const String _timeSyncRttCharacteristicUuid = @@ -58,11 +60,11 @@ final VersionConstraint _versionConstraint = /// as well as health and energy status. class OpenEarableV2 extends BluetoothWearable with - DeviceFirmwareVersionNumberExt, - BatteryLevelStatusGattReader, - BatteryLevelStatusServiceGattReader, - BatteryHealthStatusGattReader, - BatteryEnergyStatusGattReader + DeviceFirmwareVersionNumberExt, + BatteryLevelStatusGattReader, + BatteryLevelStatusServiceGattReader, + BatteryHealthStatusGattReader, + BatteryEnergyStatusGattReader implements SensorManager, SensorConfigurationManager, @@ -82,6 +84,15 @@ class OpenEarableV2 extends BluetoothWearable "45622510-6468-465a-b141-0b9b0f96b468"; static const String ledServiceUuid = "81040a2e-4819-11ee-be56-0242ac120002"; static const String batteryServiceUuid = "180F"; + static const Set serviceUuids = { + sensorServiceUuid, + parseInfoServiceUuid, + deviceInfoServiceUuid, + ledServiceUuid, + batteryServiceUuid, + _buttonServiceUuid, + _audioConfigServiceUuid, + }; final List _sensors; final List _sensorConfigurations; @@ -191,7 +202,6 @@ class OpenEarableV2 extends BluetoothWearable StreamSubscription? _sensorConfigSubscription; StreamSubscription? _buttonSubscription; - @override final Set availableMicrophones; @override @@ -688,6 +698,13 @@ class OpenEarableV2TimeSyncImp implements TimeSynchronizable { @override Future synchronizeTime() async { + if (kIsWeb) { + logger.i( + 'Skipping OpenEarable V2 time synchronization on web because the packet format uses Uint64 serialization, which is unsupported by dart2js.', + ); + return; + } + logger.i("Synchronizing time with OpenEarable V2 device..."); // Will complete when we have enough samples and wrote the final offset. @@ -700,10 +717,10 @@ class OpenEarableV2TimeSyncImp implements TimeSynchronizable { late final StreamSubscription> rttSub; rttSub = bleManager .subscribe( - deviceId: deviceId, - serviceId: timeSynchronizationServiceUuid, - characteristicId: _timeSyncRttCharacteristicUuid, - ) + deviceId: deviceId, + serviceId: timeSynchronizationServiceUuid, + characteristicId: _timeSyncRttCharacteristicUuid, + ) .listen( (data) async { final t4 = DateTime.now().microsecondsSinceEpoch; @@ -715,8 +732,9 @@ class OpenEarableV2TimeSyncImp implements TimeSynchronizable { logger.d("Received time sync response packet: $pkt"); - final t1 = pkt.timePhoneSend; // phone send timestamp (µs) - final t3 = pkt.timeDeviceSend; // device send timestamp (µs, device clock) + final t1 = pkt.timePhoneSend; // phone send timestamp (µs) + final t3 = + pkt.timeDeviceSend; // device send timestamp (µs, device clock) // Estimate Unix time at the moment the device sent the response. // Use midpoint between T1 and T4 as an estimate of when the device was "in the middle". @@ -754,7 +772,9 @@ class OpenEarableV2TimeSyncImp implements TimeSynchronizable { } }, onError: (error, stack) async { - logger.e("Error during time sync subscription $error, $stack",); + logger.e( + "Error during time sync subscription $error, $stack", + ); if (!completer.isCompleted) { completer.completeError(error, stack); } diff --git a/lib/src/models/devices/open_ring_factory.dart b/lib/src/models/devices/open_ring_factory.dart index 9fd1bb5d..c40f2ff0 100644 --- a/lib/src/models/devices/open_ring_factory.dart +++ b/lib/src/models/devices/open_ring_factory.dart @@ -18,6 +18,11 @@ import 'open_ring.dart'; import 'wearable.dart'; class OpenRingFactory extends WearableFactory { + @override + Set get usedServiceUuids => const { + OpenRingGatt.service, + }; + @override Future createFromDevice( DiscoveredDevice device, { diff --git a/lib/src/models/devices/polar_factory.dart b/lib/src/models/devices/polar_factory.dart index dbbd6392..c0ab8c6f 100644 --- a/lib/src/models/devices/polar_factory.dart +++ b/lib/src/models/devices/polar_factory.dart @@ -15,7 +15,16 @@ class PolarFactory extends WearableFactory { static const String _namePrefix = "Polar"; @override - Future createFromDevice(DiscoveredDevice device, { Set options = const {} }) async { + Set get usedServiceUuids => const { + Polar.disServiceUuid, + Polar.heartRateServiceUuid, + }; + + @override + Future createFromDevice( + DiscoveredDevice device, { + Set options = const {}, + }) async { if (bleManager == null) { throw Exception("bleManager needs to be set before using the factory"); } diff --git a/lib/src/models/wearable_factory.dart b/lib/src/models/wearable_factory.dart index db4e6820..e0fc465f 100644 --- a/lib/src/models/wearable_factory.dart +++ b/lib/src/models/wearable_factory.dart @@ -16,8 +16,18 @@ abstract class WearableFactory { /// It is provided by the [WearableManager] and should not be set directly. WearableDisconnectNotifier? disconnectNotifier; + /// BLE service UUIDs that this factory may need after selecting a device. + /// + /// Web Bluetooth requires services to be requested before a wearable instance + /// can be created, so factories declare the service set up front. + Set get usedServiceUuids => const {}; + /// Checks if the factory can create a wearable from the given device and services. Future matches(DiscoveredDevice device, List services); + /// Creates a wearable from the given device. - Future createFromDevice(DiscoveredDevice device, { Set options = const {} }); + Future createFromDevice( + DiscoveredDevice device, { + Set options = const {}, + }); } 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 78f8b9be..07b03df8 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:flutter/foundation.dart' show kIsWeb; import 'package:open_earable_flutter/open_earable_flutter.dart' show logger; import 'package:open_earable_flutter/src/constants.dart'; @@ -29,10 +30,20 @@ class V2SensorSchemeReader extends SensorSchemeReader { } int sensorIdCount = sensorIdBuffer[0]; - List sensorIds = sensorIdBuffer.sublist(1, sensorIdCount + 1); + if (sensorIdBuffer.length < 1 + sensorIdCount) { + logger.w( + "Sensor id buffer shorter than expected (count=$sensorIdCount, len=${sensorIdBuffer.length}).", + ); + } + + List sensorIds = sensorIdBuffer.length >= 1 + sensorIdCount + ? sensorIdBuffer.sublist(1, sensorIdCount + 1) + : sensorIdBuffer.sublist(1); _sensorIds.clear(); _sensorIds.addAll(sensorIds); + + logger.d("Parsed sensor ids: $sensorIds (count: $sensorIdCount)"); } @override @@ -55,10 +66,8 @@ class V2SensorSchemeReader extends SensorSchemeReader { characteristicId: sensorSchemeCharacteristicUuid, ); - final Future> responseFuture = stream - .cast>() - .first - .timeout(const Duration(seconds: 5)); + 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( @@ -70,16 +79,23 @@ class V2SensorSchemeReader extends SensorSchemeReader { try { final value = await responseFuture; - logger.d("Received notification for sensor scheme of sensor $sensorId: $value"); + 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}", + if (scheme.sensorId == 0 && sensorId != 0) { + logger.w( + "Sensor scheme response for sensor $sensorId omitted the sensor id. Using the requested id.", + ); + scheme.sensorId = sensorId; + } else if (scheme.sensorId != sensorId) { + logger.w( + "Sensor scheme response for sensor $sensorId reported sensor id ${scheme.sensorId}. Using the returned scheme.", ); } - _sensorSchemes[sensorId] = scheme; + _sensorSchemes[scheme.sensorId] = scheme; return scheme; } on TimeoutException catch (e) { throw TimeoutException("Timeout while waiting for sensor scheme: $e"); @@ -94,11 +110,29 @@ class V2SensorSchemeReader extends SensorSchemeReader { for (int sensorId in _sensorIds) { if (!_sensorSchemes.containsKey(sensorId) || forceRead) { - SensorScheme scheme = await getSchemeForSensor(sensorId); - _sensorSchemes[sensorId] = scheme; + try { + SensorScheme scheme = await getSchemeForSensor(sensorId); + _sensorSchemes[scheme.sensorId] = scheme; + } catch (e) { + logger.e( + "Failed to read sensor scheme for sensor $sensorId: $e${kIsWeb ? ' (on web platform)' : ''}", + ); + if (kIsWeb) { + logger.d( + "Skipping sensor $sensorId due to read failure on web. " + "This may be a BLE notification timeout or subscription issue.", + ); + } + // Continue with next sensor instead of failing entirely + continue; + } } } + logger.d( + "Successfully read ${_sensorSchemes.length} sensor scheme(s): " + "${_sensorSchemes.keys.join(', ')}", + ); return _sensorSchemes.values.toList(); } @@ -144,8 +178,12 @@ class V2SensorSchemeReader extends SensorSchemeReader { String unitName = utf8.decode(unitNameBytes); currentIndex += unitNameLength; - Component component = - Component(ParseType.fromInt(componentType), groupName, componentName, unitName); + Component component = Component( + ParseType.fromInt(componentType), + groupName, + componentName, + unitName, + ); sensorScheme.components.add(component); } @@ -175,7 +213,11 @@ class V2SensorSchemeReader extends SensorSchemeReader { freqs.add(byteData.getFloat32(0, Endian.little)); } currentIndex += frequencyCount * 4; - frequencies = SensorConfigFrequencies(maxStreamingFreqIndex, defaultFreqIndex, freqs); + frequencies = SensorConfigFrequencies( + maxStreamingFreqIndex, + defaultFreqIndex, + freqs, + ); } sensorScheme.options = SensorConfigOptions(features, frequencies); diff --git a/lib/src/utils/sensor_value_parser/v2_sensor_value_parser.dart b/lib/src/utils/sensor_value_parser/v2_sensor_value_parser.dart index 9b4e7c27..4a9b2d0c 100644 --- a/lib/src/utils/sensor_value_parser/v2_sensor_value_parser.dart +++ b/lib/src/utils/sensor_value_parser/v2_sensor_value_parser.dart @@ -25,7 +25,7 @@ class V2SensorValueParser extends SensorValueParser { ); _requireBytes(data, i, 8, 'timestamp'); - final baseTimestamp = data.getUint64(i, Endian.little); + final baseTimestamp = _readUint64(data, i); i += 8; // Precompute size of one component payload for efficiency. @@ -137,6 +137,12 @@ int _getTimeDiff(ByteData data) { return data.getUint16(data.lengthInBytes - 2, Endian.little); } +int _readUint64(ByteData data, int index) { + final low = data.getUint32(index, Endian.little); + final high = data.getUint32(index + 4, Endian.little); + return high * 0x100000000 + low; +} + _ParsedSample _parseSample({ required ByteData data, required int startIndex, diff --git a/pubspec.yaml b/pubspec.yaml index eaeb84f2..96bd3d8e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: open_earable_flutter description: This package provides functionality for interacting with OpenEarable devices. Control LED colors, control audio, and access raw sensor data. -version: 2.3.8 +version: 2.3.9 repository: https://github.com/OpenEarable/open_earable_flutter/tree/main platforms: