From 28b0bc6b7ce1949e1f263285a60b671705eedc61 Mon Sep 17 00:00:00 2001 From: Rohit Sangwan Date: Sun, 21 Jul 2024 20:35:29 +0530 Subject: [PATCH] Add platform specific options in start scan (#65) * Add platform specific options in start scan * Add code level doc and ChangeLog * Minor improvements --- .vscode/launch.json | 16 -- CHANGELOG.md | 4 + example/lib/data/mock_universal_ble.dart | 5 +- .../peripheral_detail_page.dart | 11 +- lib/src/models/model_exports.dart | 1 + lib/src/models/platform_config.dart | 23 +++ lib/src/universal_ble.dart | 6 +- .../universal_ble_linux.dart | 1 + .../universal_ble_pigeon_channel.dart | 1 + lib/src/universal_ble_platform_interface.dart | 1 + .../universal_ble_web/universal_ble_web.dart | 137 ++++++++++-------- 11 files changed, 123 insertions(+), 83 deletions(-) create mode 100644 lib/src/models/platform_config.dart diff --git a/.vscode/launch.json b/.vscode/launch.json index d2cbd15..f944144 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -32,22 +32,6 @@ "--web-hostname=127.0.0.1", "--web-port=8080" ], - "preLaunchTask": "open_chrome" }, ], - "tasks": [ - { - "label": "open_chrome", - "type": "shell", - "command": "open", - "args": [ - "-na", - "Google Chrome", - "--args", - "--user-data-dir=/tmp/temporary-chrome-profile-dir", - "--disable-web-security", - "--disable-site-isolation-trials" - ], - } - ], } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6675bd6..aa5c087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.11.2 +* Add `PlatformConfig` property in `StartScan` +* Add `WebConfig` property in `PlatformConfig` + ## 0.11.1 * Trim spaces in UUIDs * Receive advertisement events on web diff --git a/example/lib/data/mock_universal_ble.dart b/example/lib/data/mock_universal_ble.dart index 15e547b..9d58fd5 100644 --- a/example/lib/data/mock_universal_ble.dart +++ b/example/lib/data/mock_universal_ble.dart @@ -23,7 +23,10 @@ class MockUniversalBle extends UniversalBlePlatform { ]); @override - Future startScan({ScanFilter? scanFilter}) async => + Future startScan({ + ScanFilter? scanFilter, + PlatformConfig? platformConfig, + }) async => onScanResult?.call(_mockBleDevice); @override diff --git a/example/lib/peripheral_details/peripheral_detail_page.dart b/example/lib/peripheral_details/peripheral_detail_page.dart index 513cda5..a6e0a2f 100644 --- a/example/lib/peripheral_details/peripheral_detail_page.dart +++ b/example/lib/peripheral_details/peripheral_detail_page.dart @@ -102,11 +102,16 @@ class _PeripheralDetailPageState extends State { }); if (kIsWeb) { - _addLog("DiscoverServices", - '${services.length} services discovered,\nNote: Only services added in ScanFilter will be discovered'); + _addLog( + "DiscoverServices", + '${services.length} services discovered,\nNote: Only services added in ScanFilter or WebConfig will be discovered', + ); } } catch (e) { - _addLog("DiscoverServicesError", e); + _addLog( + "DiscoverServicesError", + '$e\nNote: Only services added in ScanFilter or WebConfig will be discovered', + ); } } diff --git a/lib/src/models/model_exports.dart b/lib/src/models/model_exports.dart index 5767122..1671518 100644 --- a/lib/src/models/model_exports.dart +++ b/lib/src/models/model_exports.dart @@ -1,3 +1,4 @@ +export 'package:universal_ble/src/models/platform_config.dart'; export 'package:universal_ble/src/models/queue_type.dart'; export 'package:universal_ble/src/models/ble_uuid_parser.dart'; export 'package:universal_ble/src/models/scan_filter.dart'; diff --git a/lib/src/models/platform_config.dart b/lib/src/models/platform_config.dart new file mode 100644 index 0000000..c8097a2 --- /dev/null +++ b/lib/src/models/platform_config.dart @@ -0,0 +1,23 @@ +/// Platform specific config to scan devices +class PlatformConfig { + WebConfig? web; + + PlatformConfig({this.web}); +} + +/// Web config to scan devices +/// [optionalServices] is a list of service uuid's to ensure that you can access the specified services after connecting to the device, +/// by default services from scanFilter will be used +/// [optionalManufacturerData] is list of `CompanyIdentifier's` and used to add `ManufacturerData` in advertisement results of selected device from web dialog, +/// by default manufacturerData from scanFilter will be used +/// Checkout more details on [web](https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/requestDevice) +/// Note: you will only get advertisements if Experimental Flag is enabled in the browser +class WebConfig { + List optionalServices; + List optionalManufacturerData; + + WebConfig({ + this.optionalServices = const [], + this.optionalManufacturerData = const [], + }); +} diff --git a/lib/src/universal_ble.dart b/lib/src/universal_ble.dart index 4aeb846..bd32821 100644 --- a/lib/src/universal_ble.dart +++ b/lib/src/universal_ble.dart @@ -49,9 +49,13 @@ class UniversalBle { /// `webRequestOptions` is supported on Web only static Future startScan({ ScanFilter? scanFilter, + PlatformConfig? platformConfig, }) async { return await _bleCommandQueue.queueCommand( - () => _platform.startScan(scanFilter: scanFilter), + () => _platform.startScan( + scanFilter: scanFilter, + platformConfig: platformConfig, + ), withTimeout: false, ); } diff --git a/lib/src/universal_ble_linux/universal_ble_linux.dart b/lib/src/universal_ble_linux/universal_ble_linux.dart index 84891c2..eb69bb3 100644 --- a/lib/src/universal_ble_linux/universal_ble_linux.dart +++ b/lib/src/universal_ble_linux/universal_ble_linux.dart @@ -55,6 +55,7 @@ class UniversalBleLinux extends UniversalBlePlatform { @override Future startScan({ ScanFilter? scanFilter, + PlatformConfig? platformConfig, }) async { await _ensureInitialized(); await super.startScan(scanFilter: scanFilter); diff --git a/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart b/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart index 75b63bb..2a04977 100644 --- a/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart +++ b/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart @@ -30,6 +30,7 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { @override Future startScan({ ScanFilter? scanFilter, + PlatformConfig? platformConfig, }) async { await super.startScan(scanFilter: scanFilter); await _channel.startScan( diff --git a/lib/src/universal_ble_platform_interface.dart b/lib/src/universal_ble_platform_interface.dart index 8faa168..2e85660 100644 --- a/lib/src/universal_ble_platform_interface.dart +++ b/lib/src/universal_ble_platform_interface.dart @@ -11,6 +11,7 @@ abstract class UniversalBlePlatform { Future startScan({ ScanFilter? scanFilter, + PlatformConfig? platformConfig, }) async { _scanFilter = scanFilter; } diff --git a/lib/src/universal_ble_web/universal_ble_web.dart b/lib/src/universal_ble_web/universal_ble_web.dart index 4c603c7..26dd1c7 100644 --- a/lib/src/universal_ble_web/universal_ble_web.dart +++ b/lib/src/universal_ble_web/universal_ble_web.dart @@ -116,17 +116,10 @@ class UniversalBleWeb extends UniversalBlePlatform { @override Future startScan({ ScanFilter? scanFilter, + PlatformConfig? platformConfig, }) async { - if (scanFilter == null || scanFilter.withServices.isEmpty) { - UniversalBlePlatform.logInfo( - "Service list empty: on web you have to specify services in the ScanFilter in order to be able to access those after connecting", - isError: true, - ); - } - BluetoothDevice device = await FlutterWebBluetooth.instance.requestDevice( - scanFilter?.toRequestOptionsBuilder() ?? - RequestOptionsBuilder.acceptAllDevices(), + _getRequestOptionBuilder(scanFilter, platformConfig?.web), ); // Update local device list @@ -158,7 +151,7 @@ class UniversalBleWeb extends UniversalBlePlatform { device.toBleScanResult( rssi: event.rssi, manufacturerDataMap: event.manufacturerData, - services: event.uuids, + services: event.uuids.toSet().toList(), ), ); }); @@ -366,6 +359,78 @@ class UniversalBleWeb extends UniversalBlePlatform { Future enableBluetooth() { throw UnimplementedError(); } + + RequestOptionsBuilder _getRequestOptionBuilder( + ScanFilter? scanFilter, + WebConfig? webConfig, + ) { + List filters = []; + List optionalManufacturerData = []; + List optionalServices = []; + + if (webConfig != null) { + optionalServices.addAll(webConfig.optionalServices.toValidUUIDList()); + optionalManufacturerData.addAll(webConfig.optionalManufacturerData); + } + + if (scanFilter != null) { + // Add services filter + for (var service in scanFilter.withServices.toValidUUIDList()) { + filters.add(RequestFilterBuilder(services: [service])); + if (webConfig == null || webConfig.optionalServices.isEmpty) { + optionalServices.add(service); + } + } + + // Add manufacturer data filter + for (var manufacturerData in scanFilter.withManufacturerData) { + filters.add( + RequestFilterBuilder( + manufacturerData: [ + ManufacturerDataFilterBuilder( + companyIdentifier: manufacturerData.companyIdentifier, + dataPrefix: manufacturerData.data, + mask: manufacturerData.mask, + ), + ], + ), + ); + + // Add optionalManufacturerData from scanFilter if webConfig is not provided + if (webConfig == null || webConfig.optionalManufacturerData.isEmpty) { + int? companyId = manufacturerData.companyIdentifier; + if (companyId != null) { + optionalManufacturerData.add(companyId); + } + } + } + + // Add name filter + for (var name in scanFilter.withNamePrefix) { + filters.add(RequestFilterBuilder(namePrefix: name)); + } + } + + if (optionalServices.isEmpty) { + UniversalBlePlatform.logInfo( + "OptionalServices list is empty on web, you have to specify services in the ScanFilter in order to be able to access those after connecting", + isError: true, + ); + } + + if (filters.isEmpty) { + return RequestOptionsBuilder.acceptAllDevices( + optionalServices: optionalServices, + optionalManufacturerData: optionalManufacturerData, + ); + } else { + return RequestOptionsBuilder( + filters, + optionalServices: optionalServices, + optionalManufacturerData: optionalManufacturerData, + ); + } + } } extension _BluetoothDeviceExtension on BluetoothDevice { @@ -398,55 +463,3 @@ extension _UnmodifiableMapViewExtension on UnmodifiableMapView { return Uint8List.fromList(bytes + manufacturerDataValue); } } - -extension ScanFilterExtension on ScanFilter { - RequestOptionsBuilder toRequestOptionsBuilder() { - List filters = []; - List manufacturerCompanyIdentifiers = []; - - // Add services filter - for (var service in withServices.toValidUUIDList()) { - filters.add( - RequestFilterBuilder( - services: [service], - ), - ); - } - - // Add manufacturer data filter - for (var manufacturerData in withManufacturerData) { - filters.add( - RequestFilterBuilder( - manufacturerData: [ - ManufacturerDataFilterBuilder( - companyIdentifier: manufacturerData.companyIdentifier, - dataPrefix: manufacturerData.data, - mask: manufacturerData.mask, - ), - ], - ), - ); - int? companyId = manufacturerData.companyIdentifier; - if (companyId != null) { - manufacturerCompanyIdentifiers.add(companyId); - } - } - - // Add name filter - for (var name in withNamePrefix) { - filters.add( - RequestFilterBuilder(namePrefix: name), - ); - } - - if (filters.isEmpty) { - return RequestOptionsBuilder.acceptAllDevices(); - } - - return RequestOptionsBuilder( - filters, - optionalServices: withServices.toValidUUIDList(), - optionalManufacturerData: manufacturerCompanyIdentifiers, - ); - } -}