From 21705880452f2b0375e5a76238bba7884e0fa59f Mon Sep 17 00:00:00 2001 From: Rohit Sangwan Date: Tue, 8 Jul 2025 11:43:45 +0530 Subject: [PATCH] Fix characteristic value listener and add tests (#168) * Improve characteristic value listener * Implement characteristic tests * Bump version and update changelog --- CHANGELOG.md | 3 + example/pubspec.lock | 2 +- lib/src/universal_ble_platform_interface.dart | 17 ++- pubspec.yaml | 2 +- test/ble_characteristic_test.dart | 127 ++++++++++++++++++ test/universal_ble_test_mock.dart | 98 ++++++++++++++ 6 files changed, 240 insertions(+), 9 deletions(-) create mode 100644 test/ble_characteristic_test.dart create mode 100644 test/universal_ble_test_mock.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 80db357..728506e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.20.2 +* Fix `BleCharacteristic.onValueReceived` + ## 0.20.1 * Fix `writeWithoutResponse()` which was not properly waiting for completion on Android and Apple diff --git a/example/pubspec.lock b/example/pubspec.lock index 8755957..32c082d 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -402,7 +402,7 @@ packages: path: ".." relative: true source: path - version: "0.20.0" + version: "0.20.1" vector_math: dependency: transitive description: diff --git a/lib/src/universal_ble_platform_interface.dart b/lib/src/universal_ble_platform_interface.dart index 532ebfd..16a7d64 100644 --- a/lib/src/universal_ble_platform_interface.dart +++ b/lib/src/universal_ble_platform_interface.dart @@ -94,10 +94,14 @@ abstract class UniversalBlePlatform { .map((e) => e.isConnected); Stream characteristicValueStream( - String deviceId, String characteristicId) => - _valueStreamController.stream.where((e) { - return e.deviceId == deviceId && e.characteristicId == characteristicId; - }).map((e) => e.value); + String deviceId, + String characteristicId, + ) { + characteristicId = BleUuidParser.string(characteristicId); + return _valueStreamController.stream.where((e) { + return e.deviceId == deviceId && e.characteristicId == characteristicId; + }).map((e) => e.value); + } Stream pairingStateStream(String deviceId) => _pairStateStreamController.stream @@ -134,15 +138,14 @@ abstract class UniversalBlePlatform { String characteristicId, Uint8List value, ) { + characteristicId = BleUuidParser.string(characteristicId); _valueStreamController.add(( deviceId: deviceId, characteristicId: characteristicId, value: value, )); - try { - onValueChange?.call( - deviceId, BleUuidParser.string(characteristicId), value); + onValueChange?.call(deviceId, characteristicId, value); } catch (_) {} } diff --git a/pubspec.yaml b/pubspec.yaml index 34b97ee..70404de 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: universal_ble description: A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter -version: 0.20.1 +version: 0.20.2 homepage: https://navideck.com repository: https://github.com/Navideck/universal_ble issue_tracker: https://github.com/Navideck/universal_ble/issues diff --git a/test/ble_characteristic_test.dart b/test/ble_characteristic_test.dart new file mode 100644 index 0000000..db189b4 --- /dev/null +++ b/test/ble_characteristic_test.dart @@ -0,0 +1,127 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:universal_ble/universal_ble.dart'; +import 'universal_ble_test_mock.dart'; + +String serviceId = "180a"; +String characteristicId = "202a"; +String mockDeviceId = "mock_id"; +BleCharacteristic mockBleCharacteristic = BleCharacteristic.withMetaData( + deviceId: mockDeviceId, + serviceId: serviceId, + uuid: characteristicId, + properties: CharacteristicProperty.values, +); +BleService mockBleService = BleService(serviceId, [mockBleCharacteristic]); + +void main() { + late UniversalBlePlatform platform; + setUp(() { + platform = _UniversalBleMock(); + UniversalBle.setInstance(platform); + }); + + group('BleCharacteristic Tests', () { + test("DiscoverServices test", () async { + debugPrint("Discovering services"); + List services = + await UniversalBle.discoverServices(mockDeviceId); + expect(services.length, 1); + + BleService service = services.first; + expect(BleUuidParser.compareStrings(service.uuid, serviceId), true); + expect(service.characteristics.length, 1); + + BleCharacteristic characteristic = service.characteristics.first; + expect( + BleUuidParser.compareStrings(characteristic.uuid, characteristicId), + true, + ); + expect(characteristic.metaData?.deviceId, mockDeviceId); + expect( + BleUuidParser.compareStrings( + characteristic.metaData!.serviceId, + serviceId, + ), + true, + ); + }); + + test("Subscription Test", () async { + BleCharacteristic characteristic = mockBleCharacteristic; + + debugPrint("Subscribing to char"); + await characteristic.notifications.subscribe(); + + bool gotEvent = false; + var subscription = characteristic.notifications.listen((data) { + debugPrint("Received CharValue: $data"); + gotEvent = true; + }); + + await Future.delayed(Duration(seconds: 1)); + await characteristic.notifications.unsubscribe(); + debugPrint("Unsubscribed from char"); + + subscription.cancel(); + expect(gotEvent, true); + }); + }); + + test("Write/Read Value Test", () async { + BleCharacteristic characteristic = mockBleCharacteristic; + Uint8List charValue = Uint8List.fromList([0x01, 0x02]); + await characteristic.write(charValue); + debugPrint("Write Succeed"); + + var readResult = await characteristic.read(); + debugPrint("Read Succeed"); + expect(readResult, charValue); + }); +} + +class _UniversalBleMock extends UniversalBlePlatformMock { + Timer? notifierTimer; + Uint8List? charValue; + + @override + Future> discoverServices(String deviceId) async { + return [mockBleService]; + } + + @override + Future setNotifiable(String deviceId, String service, + String characteristic, BleInputProperty bleInputProperty) async { + if (bleInputProperty == BleInputProperty.disabled) { + notifierTimer?.cancel(); + notifierTimer = null; + return; + } + + notifierTimer ??= Timer.periodic(Duration(milliseconds: 500), (timer) { + updateCharacteristicValue( + deviceId, + characteristic, + Uint8List.fromList([1, 2, 3]), + ); + }); + } + + @override + Future writeValue( + String deviceId, + String service, + String characteristic, + Uint8List value, + BleOutputProperty bleOutputProperty) async { + charValue = value; + } + + @override + Future readValue( + String deviceId, String service, String characteristic, + {Duration? timeout}) async { + return charValue ?? Uint8List(0); + } +} diff --git a/test/universal_ble_test_mock.dart b/test/universal_ble_test_mock.dart new file mode 100644 index 0000000..c940346 --- /dev/null +++ b/test/universal_ble_test_mock.dart @@ -0,0 +1,98 @@ +import 'dart:typed_data'; +import 'package:universal_ble/universal_ble.dart'; + +abstract class UniversalBlePlatformMock extends UniversalBlePlatform { + @override + Future connect(String deviceId, {Duration? connectionTimeout}) { + throw UnimplementedError(); + } + + @override + Future disableBluetooth() { + throw UnimplementedError(); + } + + @override + Future disconnect(String deviceId) { + throw UnimplementedError(); + } + + @override + Future> discoverServices(String deviceId) { + throw UnimplementedError(); + } + + @override + Future enableBluetooth() { + throw UnimplementedError(); + } + + @override + Future getBluetoothAvailabilityState() { + throw UnimplementedError(); + } + + @override + Future getConnectionState(String deviceId) { + throw UnimplementedError(); + } + + @override + Future> getSystemDevices(List? withServices) { + throw UnimplementedError(); + } + + @override + Future isPaired(String deviceId) { + throw UnimplementedError(); + } + + @override + Future pair(String deviceId) { + throw UnimplementedError(); + } + + @override + Future readValue( + String deviceId, String service, String characteristic, + {Duration? timeout}) { + throw UnimplementedError(); + } + + @override + Future requestMtu(String deviceId, int expectedMtu) { + throw UnimplementedError(); + } + + @override + Future setNotifiable(String deviceId, String service, + String characteristic, BleInputProperty bleInputProperty) { + throw UnimplementedError(); + } + + @override + Future startScan( + {ScanFilter? scanFilter, PlatformConfig? platformConfig}) { + throw UnimplementedError(); + } + + @override + Future stopScan() { + throw UnimplementedError(); + } + + @override + Future unpair(String deviceId) { + throw UnimplementedError(); + } + + @override + Future writeValue( + String deviceId, + String service, + String characteristic, + Uint8List value, + BleOutputProperty bleOutputProperty) { + throw UnimplementedError(); + } +}