From 1f7ba287ef91821b399ca3ed15611615cb27f03d Mon Sep 17 00:00:00 2001 From: Rohit Sangwan Date: Tue, 5 Mar 2024 22:37:14 +0530 Subject: [PATCH] Implement ScanFilter (#17) * Implement ScanFilter * Add windows implementation * Fix Linux and cleanups * Update Readme, Changelog and Version * Minor fixes from AI comments * Fix windows crash and update pigeon * Fix UUID parsing * Apply suggestions from code review Co-authored-by: Foti Dim --------- Co-authored-by: Foti Dim --- .vscode/settings.json | 4 +- CHANGELOG.md | 5 + README.md | 7 + .../navideck/universal_ble/UniversalBle.g.kt | 45 ++++- .../universal_ble/UniversalBleHelper.kt | 37 ++++ .../universal_ble/UniversalBlePlugin.kt | 24 ++- darwin/Classes/UniversalBle.g.swift | 39 +++- darwin/Classes/UniversalBleHelper.swift | 14 ++ darwin/Classes/UniversalBlePlugin.swift | 20 +- example/lib/data/mock_universal_ble.dart | 5 +- example/lib/home/home.dart | 5 +- lib/src/models/ble_scan_result.dart | 2 + lib/src/models/model_exports.dart | 2 + lib/src/models/scan_filter.dart | 7 + lib/src/models/uuid.dart | 34 ++++ .../models/web_request_options_builder.dart | 44 +++-- lib/src/universal_ble.dart | 7 +- .../universal_ble_linux.dart | 13 +- .../universal_ble_pigeon/universal_ble.g.dart | 37 +++- .../universal_ble_pigeon_channel.dart | 25 ++- lib/src/universal_ble_platform_interface.dart | 2 +- .../universal_ble_web/universal_ble_web.dart | 27 ++- pigeon/universal_ble.dart | 10 +- pubspec.yaml | 4 +- windows/src/generated/universal_ble.g.cpp | 177 ++++++++++++------ windows/src/generated/universal_ble.g.h | 34 +++- windows/src/universal_ble_plugin.cpp | 31 ++- windows/src/universal_ble_plugin.h | 3 +- 28 files changed, 546 insertions(+), 118 deletions(-) create mode 100644 lib/src/models/scan_filter.dart create mode 100644 lib/src/models/uuid.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 3e5a36d..bf22154 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "cSpell.words": [ + "bluez", "BTLE", "bytevc", "connnection", @@ -25,7 +26,8 @@ "unwatch", "uuidstr", "winrt", - "WPARAM" + "WPARAM", + "ZUUID" ], "cmake.configureOnOpen": false, "files.associations": { diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f8cebc..0e92c43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.9.3 +* Add scan filter (withServices:) in `startScan()` +* Add service UUIDs from advertisements in `BleScanResult` +* Fix Windows release build compilation + ## 0.9.2 * Add command queue * Improve error handling on Android, iOS, macOS, Windows, Linux and web diff --git a/README.md b/README.md index b3a35db..2e42ada 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,13 @@ UniversalBle.onScanResult = (scanResult) { // Perform a scan UniversalBle.startScan(); +// Or optionally add a scan filter +UniversalBle.startScan( + scanFilter: ScanFilter( + withServices: ["SERVICE_UUID"], + ) +); + // Stop scanning UniversalBle.stopScan(); ``` diff --git a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt index 0def244..0695d97 100644 --- a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt +++ b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v16.0.5), do not edit directly. +// Autogenerated from Pigeon (v17.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon package com.navideck.universal_ble @@ -53,7 +53,8 @@ data class UniversalBleScanResult ( val isPaired: Boolean? = null, val rssi: Long? = null, val manufacturerData: ByteArray? = null, - val manufacturerDataHead: ByteArray? = null + val manufacturerDataHead: ByteArray? = null, + val services: List? = null ) { companion object { @@ -65,7 +66,8 @@ data class UniversalBleScanResult ( val rssi = list[3].let { if (it is Int) it.toLong() else it as Long? } val manufacturerData = list[4] as ByteArray? val manufacturerDataHead = list[5] as ByteArray? - return UniversalBleScanResult(deviceId, name, isPaired, rssi, manufacturerData, manufacturerDataHead) + val services = list[6] as List? + return UniversalBleScanResult(deviceId, name, isPaired, rssi, manufacturerData, manufacturerDataHead, services) } } fun toList(): List { @@ -76,6 +78,7 @@ data class UniversalBleScanResult ( rssi, manufacturerData, manufacturerDataHead, + services, ) } } @@ -124,6 +127,25 @@ data class UniversalBleCharacteristic ( } } +/** Generated class from Pigeon that represents data sent in messages. */ +data class UniversalScanFilter ( + val withServices: List + +) { + companion object { + @Suppress("UNCHECKED_CAST") + fun fromList(list: List): UniversalScanFilter { + val withServices = list[0] as List + return UniversalScanFilter(withServices) + } + } + fun toList(): List { + return listOf( + withServices, + ) + } +} + @Suppress("UNCHECKED_CAST") private object UniversalBlePlatformChannelCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { @@ -143,6 +165,11 @@ private object UniversalBlePlatformChannelCodec : StandardMessageCodec() { UniversalBleService.fromList(it) } } + 131.toByte() -> { + return (readValue(buffer) as? List)?.let { + UniversalScanFilter.fromList(it) + } + } else -> super.readValueOfType(type, buffer) } } @@ -160,6 +187,10 @@ private object UniversalBlePlatformChannelCodec : StandardMessageCodec() { stream.write(130) writeValue(stream, value.toList()) } + is UniversalScanFilter -> { + stream.write(131) + writeValue(stream, value.toList()) + } else -> super.writeValue(stream, value) } } @@ -173,7 +204,7 @@ private object UniversalBlePlatformChannelCodec : StandardMessageCodec() { interface UniversalBlePlatformChannel { fun getBluetoothAvailabilityState(callback: (Result) -> Unit) fun enableBluetooth(callback: (Result) -> Unit) - fun startScan() + fun startScan(filter: UniversalScanFilter?) fun stopScan() fun connect(deviceId: String) fun disconnect(deviceId: String) @@ -234,10 +265,12 @@ interface UniversalBlePlatformChannel { run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan", codec) if (api != null) { - channel.setMessageHandler { _, reply -> + channel.setMessageHandler { message, reply -> + val args = message as List + val filterArg = args[0] as UniversalScanFilter? var wrapped: List try { - api.startScan() + api.startScan(filterArg) wrapped = listOf(null) } catch (exception: Throwable) { wrapped = wrapError(exception) diff --git a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBleHelper.kt b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBleHelper.kt index 68e4321..4f1d704 100644 --- a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBleHelper.kt +++ b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBleHelper.kt @@ -13,7 +13,9 @@ import android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED import android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR import android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES import android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY +import android.bluetooth.le.ScanFilter import android.bluetooth.le.ScanResult +import android.os.ParcelUuid import android.util.Log import java.nio.ByteBuffer import java.nio.ByteOrder @@ -61,6 +63,15 @@ enum class CharacteristicProperty(val value: Long) { ExtendedProperties(7) } + +fun String.validFullUUID(): String { + return when (this.count()) { + 4 -> "0000$this-0000-1000-8000-00805F9B34FB" + 8 -> "$this-0000-1000-8000-00805F9B34FB" + else -> this + } +} + fun String.toBluetoothGatt(): BluetoothGatt { return knownGatts.find { it.device.address == this } ?: throw FlutterError("IllegalArgument", "Unknown deviceId: $this", null) @@ -132,6 +143,32 @@ fun Int.parseGattErrorCode(): String? { } } +fun UniversalScanFilter.toScanFilters(): List { + var scanFilters: List = emptyList() + + // Add withServices Filter + for (service in this.withServices) { + try { + val serviceUUID = service?.validFullUUID() + serviceUUID?.let { + val parcelUUId = ParcelUuid.fromString(it) + scanFilters = scanFilters.plus( + ScanFilter.Builder().setServiceUuid(parcelUUId).build() + ) + Log.e(TAG, "scanFilters: $parcelUUId") + } + } catch (e: Exception) { + Log.e(TAG, e.toString()) + throw FlutterError( + "illegalIllegalArgument", + "Invalid serviceId: $service", + e.toString() + ) + } + } + + return scanFilters +} val ScanResult.manufacturerDataHead: ByteArray? get() { diff --git a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePlugin.kt b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePlugin.kt index 0b07960..f25c0ad 100644 --- a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePlugin.kt +++ b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePlugin.kt @@ -6,7 +6,9 @@ import android.bluetooth.* import android.bluetooth.BluetoothDevice.BOND_BONDED import android.bluetooth.BluetoothDevice.BOND_NONE import android.bluetooth.le.ScanCallback +import android.bluetooth.le.ScanFilter import android.bluetooth.le.ScanResult +import android.bluetooth.le.ScanSettings import android.content.BroadcastReceiver import android.content.Context import android.content.Context.RECEIVER_EXPORTED @@ -15,6 +17,7 @@ import android.content.IntentFilter import android.os.Build import android.os.Handler import android.os.Looper +import android.os.ParcelUuid import android.util.Log import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware @@ -24,6 +27,7 @@ import java.util.* import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit + private const val TAG = "UniversalBlePlugin" @SuppressLint("MissingPermission") @@ -95,12 +99,24 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), bluetoothEnableRequestFuture = callback } - override fun startScan() { + override fun startScan(filter: UniversalScanFilter?) { if (!isBluetoothAvailable()) throw FlutterError( "BluetoothNotEnabled", "Bluetooth not enabled", ) - bluetoothManager.adapter.bluetoothLeScanner?.startScan(scanCallback) + + val builder = ScanSettings.Builder() + if (Build.VERSION.SDK_INT >= 26) { + builder.setPhy(ScanSettings.PHY_LE_ALL_SUPPORTED) + builder.setLegacy(false) + } + val settings = builder.build() + + bluetoothManager.adapter.bluetoothLeScanner?.startScan( + filter?.toScanFilters() ?: emptyList(), + settings, + scanCallback + ) } override fun stopScan() { @@ -656,7 +672,9 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), deviceId = result.device.address, isPaired = result.device.bondState == BOND_BONDED, manufacturerDataHead = result.manufacturerDataHead, - rssi = result.rssi.toLong() + rssi = result.rssi.toLong(), + services = result.device.uuids?.map { it.uuid?.toString() ?: "" } + ?: emptyList() ) ) {} } diff --git a/darwin/Classes/UniversalBle.g.swift b/darwin/Classes/UniversalBle.g.swift index b1a7151..59f1a2a 100644 --- a/darwin/Classes/UniversalBle.g.swift +++ b/darwin/Classes/UniversalBle.g.swift @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v16.0.5), do not edit directly. +// Autogenerated from Pigeon (v17.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -51,6 +51,7 @@ struct UniversalBleScanResult { var rssi: Int64? = nil var manufacturerData: FlutterStandardTypedData? = nil var manufacturerDataHead: FlutterStandardTypedData? = nil + var services: [String?]? = nil static func fromList(_ list: [Any?]) -> UniversalBleScanResult? { let deviceId = list[0] as! String @@ -59,6 +60,7 @@ struct UniversalBleScanResult { let rssi: Int64? = isNullish(list[3]) ? nil : (list[3] is Int64? ? list[3] as! Int64? : Int64(list[3] as! Int32)) let manufacturerData: FlutterStandardTypedData? = nilOrValue(list[4]) let manufacturerDataHead: FlutterStandardTypedData? = nilOrValue(list[5]) + let services: [String?]? = nilOrValue(list[6]) return UniversalBleScanResult( deviceId: deviceId, @@ -66,7 +68,8 @@ struct UniversalBleScanResult { isPaired: isPaired, rssi: rssi, manufacturerData: manufacturerData, - manufacturerDataHead: manufacturerDataHead + manufacturerDataHead: manufacturerDataHead, + services: services ) } func toList() -> [Any?] { @@ -77,6 +80,7 @@ struct UniversalBleScanResult { rssi, manufacturerData, manufacturerDataHead, + services, ] } } @@ -125,6 +129,24 @@ struct UniversalBleCharacteristic { } } +/// Generated class from Pigeon that represents data sent in messages. +struct UniversalScanFilter { + var withServices: [String?] + + static func fromList(_ list: [Any?]) -> UniversalScanFilter? { + let withServices = list[0] as! [String?] + + return UniversalScanFilter( + withServices: withServices + ) + } + func toList() -> [Any?] { + return [ + withServices + ] + } +} + private class UniversalBlePlatformChannelCodecReader: FlutterStandardReader { override func readValue(ofType type: UInt8) -> Any? { switch type { @@ -134,6 +156,8 @@ private class UniversalBlePlatformChannelCodecReader: FlutterStandardReader { return UniversalBleScanResult.fromList(self.readValue() as! [Any?]) case 130: return UniversalBleService.fromList(self.readValue() as! [Any?]) + case 131: + return UniversalScanFilter.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) } @@ -151,6 +175,9 @@ private class UniversalBlePlatformChannelCodecWriter: FlutterStandardWriter { } else if let value = value as? UniversalBleService { super.writeByte(130) super.writeValue(value.toList()) + } else if let value = value as? UniversalScanFilter { + super.writeByte(131) + super.writeValue(value.toList()) } else { super.writeValue(value) } @@ -177,7 +204,7 @@ class UniversalBlePlatformChannelCodec: FlutterStandardMessageCodec { protocol UniversalBlePlatformChannel { func getBluetoothAvailabilityState(completion: @escaping (Result) -> Void) func enableBluetooth(completion: @escaping (Result) -> Void) - func startScan() throws + func startScan(filter: UniversalScanFilter?) throws func stopScan() throws func connect(deviceId: String) throws func disconnect(deviceId: String) throws @@ -230,9 +257,11 @@ class UniversalBlePlatformChannelSetup { } let startScanChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan", binaryMessenger: binaryMessenger, codec: codec) if let api = api { - startScanChannel.setMessageHandler { _, reply in + startScanChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let filterArg: UniversalScanFilter? = nilOrValue(args[0]) do { - try api.startScan() + try api.startScan(filter: filterArg) reply(wrapResult(nil)) } catch { reply(wrapError(error)) diff --git a/darwin/Classes/UniversalBleHelper.swift b/darwin/Classes/UniversalBleHelper.swift index 92ee390..c724fcb 100644 --- a/darwin/Classes/UniversalBleHelper.swift +++ b/darwin/Classes/UniversalBleHelper.swift @@ -141,6 +141,20 @@ public extension CBPeripheral { } } +extension String { + var validFullUUID: String { + let uuidLength = self.count + if uuidLength == 4 || uuidLength == 8 { + let baseUuid = "00000000-0000-1000-8000-00805F9B34FB" + let start = baseUuid.startIndex + let range = baseUuid.index(start, offsetBy: 4-uuidLength).. startScan({WebRequestOptionsBuilder? webRequestOptions}) async { + Future startScan({ + WebRequestOptionsBuilder? webRequestOptions, + ScanFilter? scanFilter, + }) async { onScanResult?.call(_mockBleScanResult); } diff --git a/example/lib/home/home.dart b/example/lib/home/home.dart index 562730a..3e79e34 100644 --- a/example/lib/home/home.dart +++ b/example/lib/home/home.dart @@ -62,6 +62,7 @@ class _MyAppState extends State { }; UniversalBle.onScanResult = (result) { + // debugPrint("ScanResult: ${result.name} ${result.services}"); // debugPrint("${result.name} ${result.manufacturerData}"); int index = _scanResults.indexWhere((e) => e.deviceId == result.deviceId); if (index == -1) { @@ -110,7 +111,9 @@ class _MyAppState extends State { }); try { await UniversalBle.startScan( - webRequestOptions: _requestOptions); + webRequestOptions: _requestOptions, + // scanFilter: ScanFilter(withServices: ['180f']), + ); } catch (e) { setState(() { _isScanning = false; diff --git a/lib/src/models/ble_scan_result.dart b/lib/src/models/ble_scan_result.dart index 1fe8e51..e2ffebe 100644 --- a/lib/src/models/ble_scan_result.dart +++ b/lib/src/models/ble_scan_result.dart @@ -7,6 +7,7 @@ class BleScanResult { Uint8List? manufacturerDataHead; Uint8List? manufacturerData; int? rssi; + List services; BleScanResult({ required this.name, @@ -15,6 +16,7 @@ class BleScanResult { this.isPaired, Uint8List? manufacturerData, Uint8List? manufacturerDataHead, + this.services = const [], }) { this.manufacturerDataHead = manufacturerDataHead ?? Uint8List.fromList([]); this.manufacturerData = manufacturerData ?? manufacturerDataHead; diff --git a/lib/src/models/model_exports.dart b/lib/src/models/model_exports.dart index 85ef3fc..a1143ba 100644 --- a/lib/src/models/model_exports.dart +++ b/lib/src/models/model_exports.dart @@ -1,3 +1,5 @@ +export 'package:universal_ble/src/models/uuid.dart'; +export 'package:universal_ble/src/models/scan_filter.dart'; export 'package:universal_ble/src/models/web_request_options_builder.dart'; export 'package:universal_ble/src/models/ble_property.dart'; export 'package:universal_ble/src/models/ble_service.dart'; diff --git a/lib/src/models/scan_filter.dart b/lib/src/models/scan_filter.dart new file mode 100644 index 0000000..a0eb028 --- /dev/null +++ b/lib/src/models/scan_filter.dart @@ -0,0 +1,7 @@ +class ScanFilter { + final List withServices; + + ScanFilter({ + this.withServices = const [], + }); +} diff --git a/lib/src/models/uuid.dart b/lib/src/models/uuid.dart new file mode 100644 index 0000000..2ca8dd8 --- /dev/null +++ b/lib/src/models/uuid.dart @@ -0,0 +1,34 @@ +import 'package:bluez/bluez.dart'; + +class UUID { + late String value; + + UUID(String uuid) { + // To validate the short UUID + if (uuid.length <= 4) { + try { + int shortValue = int.parse(uuid, radix: 16); + value = BlueZUUID.short(shortValue).toString(); + return; + } catch (_) {} + } + // To validate the UUID + BlueZUUID.fromString(uuid); + value = uuid; + } + + factory UUID.fromShort(int short) { + BlueZUUID blueZUUID = BlueZUUID.short(short); + return UUID(blueZUUID.toString()); + } + + @override + String toString() { + return value; + } +} + +/// Parse a list of strings to a list of UUIDs +extension StringListToUUID on List { + List toValidUUIDList() => map((e) => UUID(e).value).toList(); +} diff --git a/lib/src/models/web_request_options_builder.dart b/lib/src/models/web_request_options_builder.dart index ff02505..27d77ef 100644 --- a/lib/src/models/web_request_options_builder.dart +++ b/lib/src/models/web_request_options_builder.dart @@ -1,20 +1,21 @@ import 'dart:typed_data'; import 'package:flutter_web_bluetooth/flutter_web_bluetooth.dart'; +import 'package:universal_ble/universal_ble.dart'; class WebRequestOptionsBuilder { final bool _acceptAllDevices; final List _requestFilters; final List? _exclusionFilters; - final List? _optionalServices; + List? _optionalServices; WebRequestOptionsBuilder( List requestFilters, { List? exclusionFilters, List? optionalServices, - }) : _requestFilters = requestFilters, + }) : _optionalServices = optionalServices, + _requestFilters = requestFilters, _exclusionFilters = exclusionFilters, - _acceptAllDevices = false, - _optionalServices = optionalServices { + _acceptAllDevices = false { if (_requestFilters.isEmpty) { throw StateError( 'No filters have been set, consider using ' @@ -26,25 +27,38 @@ class WebRequestOptionsBuilder { /// To accept all devices WebRequestOptionsBuilder.acceptAllDevices({ List? optionalServices, - }) : _acceptAllDevices = true, + }) : _optionalServices = optionalServices, + _acceptAllDevices = true, _requestFilters = [], - _exclusionFilters = null, - _optionalServices = optionalServices; + _exclusionFilters = null; /// For internal use /// To convert UniversalBleRequestOptions to FlutterWebBluetoothRequestOptions - RequestOptionsBuilder toRequestOptionsBuilder() { + RequestOptionsBuilder toRequestOptionsBuilder({ScanFilter? scanFilter}) { + if (scanFilter != null) _applyScanFilter(scanFilter); + if (_acceptAllDevices) { return RequestOptionsBuilder.acceptAllDevices( optionalServices: _optionalServices, ); - } else { - return RequestOptionsBuilder( - _requestFilters.map((e) => e.getRequestFilterBuilder()).toList(), - exclusionFilters: - _exclusionFilters?.map((e) => e.getRequestFilterBuilder()).toList(), - optionalServices: _optionalServices, - ); + } + + return RequestOptionsBuilder( + _requestFilters.map((e) => e.getRequestFilterBuilder()).toList(), + exclusionFilters: + _exclusionFilters?.map((e) => e.getRequestFilterBuilder()).toList(), + optionalServices: _optionalServices, + ); + } + + /// To apply scan filter + void _applyScanFilter(ScanFilter scanFilter) { + List filterServices = scanFilter.withServices; + _optionalServices ??= filterServices.toValidUUIDList(); + + for (var requestFilter in _requestFilters) { + // Apply services filter + requestFilter.services ??= filterServices.toValidUUIDList(); } } diff --git a/lib/src/universal_ble.dart b/lib/src/universal_ble.dart index b607ccb..5f4e5ea 100644 --- a/lib/src/universal_ble.dart +++ b/lib/src/universal_ble.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; +import 'package:universal_ble/src/models/scan_filter.dart'; import 'package:universal_ble/src/universal_ble_linux/universal_ble_linux.dart'; import 'package:universal_ble/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart'; import 'package:universal_ble/src/universal_ble_web/universal_ble_web.dart'; @@ -45,9 +46,13 @@ class UniversalBle { /// `webRequestOptions` supported on Web only static Future startScan({ WebRequestOptionsBuilder? webRequestOptions, + ScanFilter? scanFilter, }) async { return await _executeCommand( - () => _platform.startScan(webRequestOptions: webRequestOptions), + () => _platform.startScan( + webRequestOptions: webRequestOptions, + scanFilter: scanFilter, + ), timeout: null, ); } diff --git a/lib/src/universal_ble_linux/universal_ble_linux.dart b/lib/src/universal_ble_linux/universal_ble_linux.dart index ceed032..ead0e78 100644 --- a/lib/src/universal_ble_linux/universal_ble_linux.dart +++ b/lib/src/universal_ble_linux/universal_ble_linux.dart @@ -55,9 +55,14 @@ class UniversalBleLinux extends UniversalBlePlatform { @override Future startScan({ WebRequestOptionsBuilder? webRequestOptions, + ScanFilter? scanFilter, }) async { await _ensureInitialized(); if (_activeAdapter?.discovering != true) { + // Add services filter + _activeAdapter?.setDiscoveryFilter( + uuids: scanFilter?.withServices.toValidUUIDList(), + ); await _activeAdapter?.startDiscovery(); _client.devices.forEach(_onDeviceAdd); } @@ -455,11 +460,8 @@ extension BlueZDeviceExtension on BlueZDevice { int companyId = sorted.first.key.id; List manufacturerDataValue = sorted.first.value; var byteData = ByteData(2); - byteData.setInt16( - 0, - companyId, - Endian - .host); // TODO: Verify that this works regardless of the endianess + // TODO: Verify that this works regardless of the endianess + byteData.setInt16(0, companyId, Endian.host); List bytes = byteData.buffer.asUint8List(); return Uint8List.fromList(bytes + manufacturerDataValue); } catch (e) { @@ -479,6 +481,7 @@ extension BlueZDeviceExtension on BlueZDevice { manufacturerData: manufacturerDataHead, manufacturerDataHead: manufacturerDataHead, rssi: rssi, + services: uuids.map((e) => e.toString()).toList(), ); } } diff --git a/lib/src/universal_ble_pigeon/universal_ble.g.dart b/lib/src/universal_ble_pigeon/universal_ble.g.dart index 1891d79..a945522 100644 --- a/lib/src/universal_ble_pigeon/universal_ble.g.dart +++ b/lib/src/universal_ble_pigeon/universal_ble.g.dart @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v16.0.5), do not edit directly. +// Autogenerated from Pigeon (v17.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -33,6 +33,7 @@ class UniversalBleScanResult { this.rssi, this.manufacturerData, this.manufacturerDataHead, + this.services, }); String deviceId; @@ -47,6 +48,8 @@ class UniversalBleScanResult { Uint8List? manufacturerDataHead; + List? services; + Object encode() { return [ deviceId, @@ -55,6 +58,7 @@ class UniversalBleScanResult { rssi, manufacturerData, manufacturerDataHead, + services, ]; } @@ -67,6 +71,7 @@ class UniversalBleScanResult { rssi: result[3] as int?, manufacturerData: result[4] as Uint8List?, manufacturerDataHead: result[5] as Uint8List?, + services: (result[6] as List?)?.cast(), ); } } @@ -123,6 +128,27 @@ class UniversalBleCharacteristic { } } +class UniversalScanFilter { + UniversalScanFilter({ + required this.withServices, + }); + + List withServices; + + Object encode() { + return [ + withServices, + ]; + } + + static UniversalScanFilter decode(Object result) { + result as List; + return UniversalScanFilter( + withServices: (result[0] as List?)!.cast(), + ); + } +} + class _UniversalBlePlatformChannelCodec extends StandardMessageCodec { const _UniversalBlePlatformChannelCodec(); @override @@ -136,6 +162,9 @@ class _UniversalBlePlatformChannelCodec extends StandardMessageCodec { } else if (value is UniversalBleService) { buffer.putUint8(130); writeValue(buffer, value.encode()); + } else if (value is UniversalScanFilter) { + buffer.putUint8(131); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -150,6 +179,8 @@ class _UniversalBlePlatformChannelCodec extends StandardMessageCodec { return UniversalBleScanResult.decode(readValue(buffer)!); case 130: return UniversalBleService.decode(readValue(buffer)!); + case 131: + return UniversalScanFilter.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -221,7 +252,7 @@ class UniversalBlePlatformChannel { } } - Future startScan() async { + Future startScan(UniversalScanFilter? filter) async { const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan'; final BasicMessageChannel __pigeon_channel = BasicMessageChannel( __pigeon_channelName, @@ -229,7 +260,7 @@ class UniversalBlePlatformChannel { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; + await __pigeon_channel.send([filter]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { 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 7601709..758a23e 100644 --- a/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart +++ b/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart @@ -30,8 +30,12 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { @override Future startScan({ WebRequestOptionsBuilder? webRequestOptions, - }) => - _channel.startScan(); + ScanFilter? scanFilter, + }) async { + await _channel.startScan( + scanFilter.toUniversalScanFilter(), + ); + } @override Future stopScan() => _channel.stopScan(); @@ -192,6 +196,23 @@ extension _UniversalBleScanResultExtension on UniversalBleScanResult { manufacturerData: mnfData, manufacturerDataHead: mnfDataHead, rssi: rssi, + services: services + ?.where((e) => e != null) + .map((e) => UUID(e!).toString()) + .toList() ?? + [], + ); + } +} + +extension _ScanFilterExtension on ScanFilter? { + UniversalScanFilter? toUniversalScanFilter() { + // Windows crashes if it's null, so we need to pass empty scan filter in this case + if (this == null && Platform.isWindows) { + return UniversalScanFilter(withServices: []); + } + return UniversalScanFilter( + withServices: this?.withServices.toValidUUIDList() ?? [], ); } } diff --git a/lib/src/universal_ble_platform_interface.dart b/lib/src/universal_ble_platform_interface.dart index fa30ac8..aaea4eb 100644 --- a/lib/src/universal_ble_platform_interface.dart +++ b/lib/src/universal_ble_platform_interface.dart @@ -1,6 +1,5 @@ import 'dart:developer'; import 'dart:typed_data'; - import 'package:universal_ble/universal_ble.dart'; abstract class UniversalBlePlatform { @@ -10,6 +9,7 @@ abstract class UniversalBlePlatform { Future startScan({ WebRequestOptionsBuilder? webRequestOptions, + ScanFilter? scanFilter, }); Future stopScan(); diff --git a/lib/src/universal_ble_web/universal_ble_web.dart b/lib/src/universal_ble_web/universal_ble_web.dart index 82c92e7..5f58304 100644 --- a/lib/src/universal_ble_web/universal_ble_web.dart +++ b/lib/src/universal_ble_web/universal_ble_web.dart @@ -110,10 +110,21 @@ class UniversalBleWeb extends UniversalBlePlatform { @override Future startScan({ WebRequestOptionsBuilder? webRequestOptions, + ScanFilter? scanFilter, }) async { + RequestOptionsBuilder requestFilterBuilder; + if (webRequestOptions != null) { + requestFilterBuilder = webRequestOptions.toRequestOptionsBuilder( + scanFilter: scanFilter, + ); + } else if (scanFilter != null) { + requestFilterBuilder = scanFilter.toRequestOptionsBuilder(); + } else { + requestFilterBuilder = RequestOptionsBuilder.acceptAllDevices(); + } + BluetoothDevice device = await FlutterWebBluetooth.instance.requestDevice( - webRequestOptions?.toRequestOptionsBuilder() ?? - RequestOptionsBuilder.acceptAllDevices(), + requestFilterBuilder, ); // Update local device list @@ -137,6 +148,7 @@ class UniversalBleWeb extends UniversalBlePlatform { device.toBleScanResult( rssi: event.rssi, manufacturerDataMap: event.manufacturerData, + services: event.uuids, ), ); }); @@ -346,6 +358,7 @@ extension _BluetoothDeviceExtension on BluetoothDevice { BleScanResult toBleScanResult({ int? rssi, UnmodifiableMapView? manufacturerDataMap, + List services = const [], }) { return BleScanResult( name: name, @@ -353,6 +366,7 @@ extension _BluetoothDeviceExtension on BluetoothDevice { manufacturerData: manufacturerDataMap?.toUint8List(), manufacturerDataHead: manufacturerDataMap?.toUint8List(), rssi: rssi, + services: services, ); } } @@ -372,3 +386,12 @@ extension _UnmodifiableMapViewExtension on UnmodifiableMapView { return result; } } + +extension ScanFilterExtension on ScanFilter { + RequestOptionsBuilder toRequestOptionsBuilder() { + return RequestOptionsBuilder( + [RequestFilterBuilder(services: withServices.toValidUUIDList())], + optionalServices: withServices.toValidUUIDList(), + ); + } +} diff --git a/pigeon/universal_ble.dart b/pigeon/universal_ble.dart index 6abee3d..0fa228a 100644 --- a/pigeon/universal_ble.dart +++ b/pigeon/universal_ble.dart @@ -27,7 +27,7 @@ abstract class UniversalBlePlatformChannel { @async bool enableBluetooth(); - void startScan(); + void startScan(UniversalScanFilter? filter); void stopScan(); @@ -105,6 +105,7 @@ class UniversalBleScanResult { final int? rssi; final Uint8List? manufacturerData; final Uint8List? manufacturerDataHead; + final List? services; UniversalBleScanResult({ required this.name, @@ -113,6 +114,7 @@ class UniversalBleScanResult { required this.rssi, required this.manufacturerData, required this.manufacturerDataHead, + required this.services, }); } @@ -127,3 +129,9 @@ class UniversalBleCharacteristic { List properties; UniversalBleCharacteristic(this.uuid, this.properties); } + +class UniversalScanFilter { + final List withServices; + + UniversalScanFilter(this.withServices); +} diff --git a/pubspec.yaml b/pubspec.yaml index d0f4071..3a6a19b 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.9.2 +version: 0.9.3 homepage: https://navideck.com repository: https://github.com/Navideck/universal_ble issue_tracker: https://github.com/Navideck/universal_ble/issues @@ -32,7 +32,7 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 - pigeon: ^16.0.5 + pigeon: ^17.1.1 flutter: plugin: diff --git a/windows/src/generated/universal_ble.g.cpp b/windows/src/generated/universal_ble.g.cpp index 84f04eb..e078180 100644 --- a/windows/src/generated/universal_ble.g.cpp +++ b/windows/src/generated/universal_ble.g.cpp @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v16.0.5), do not edit directly. +// Autogenerated from Pigeon (v17.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon #undef _HAS_EXCEPTIONS @@ -39,13 +39,15 @@ UniversalBleScanResult::UniversalBleScanResult( const bool* is_paired, const int64_t* rssi, const std::vector* manufacturer_data, - const std::vector* manufacturer_data_head) + const std::vector* manufacturer_data_head, + const EncodableList* services) : device_id_(device_id), name_(name ? std::optional(*name) : std::nullopt), is_paired_(is_paired ? std::optional(*is_paired) : std::nullopt), rssi_(rssi ? std::optional(*rssi) : std::nullopt), manufacturer_data_(manufacturer_data ? std::optional>(*manufacturer_data) : std::nullopt), - manufacturer_data_head_(manufacturer_data_head ? std::optional>(*manufacturer_data_head) : std::nullopt) {} + manufacturer_data_head_(manufacturer_data_head ? std::optional>(*manufacturer_data_head) : std::nullopt), + services_(services ? std::optional(*services) : std::nullopt) {} const std::string& UniversalBleScanResult::device_id() const { return device_id_; @@ -121,15 +123,29 @@ void UniversalBleScanResult::set_manufacturer_data_head(const std::vector(*value_arg) : std::nullopt; +} + +void UniversalBleScanResult::set_services(const EncodableList& value_arg) { + services_ = value_arg; +} + + EncodableList UniversalBleScanResult::ToEncodableList() const { EncodableList list; - list.reserve(6); + list.reserve(7); list.push_back(EncodableValue(device_id_)); list.push_back(name_ ? EncodableValue(*name_) : EncodableValue()); list.push_back(is_paired_ ? EncodableValue(*is_paired_) : EncodableValue()); list.push_back(rssi_ ? EncodableValue(*rssi_) : EncodableValue()); list.push_back(manufacturer_data_ ? EncodableValue(*manufacturer_data_) : EncodableValue()); list.push_back(manufacturer_data_head_ ? EncodableValue(*manufacturer_data_head_) : EncodableValue()); + list.push_back(services_ ? EncodableValue(*services_) : EncodableValue()); return list; } @@ -156,6 +172,10 @@ UniversalBleScanResult UniversalBleScanResult::FromEncodableList(const Encodable if (!encodable_manufacturer_data_head.IsNull()) { decoded.set_manufacturer_data_head(std::get>(encodable_manufacturer_data_head)); } + auto& encodable_services = list[6]; + if (!encodable_services.IsNull()) { + decoded.set_services(std::get(encodable_services)); + } return decoded; } @@ -251,6 +271,33 @@ UniversalBleCharacteristic UniversalBleCharacteristic::FromEncodableList(const E return decoded; } +// UniversalScanFilter + +UniversalScanFilter::UniversalScanFilter(const EncodableList& with_services) + : with_services_(with_services) {} + +const EncodableList& UniversalScanFilter::with_services() const { + return with_services_; +} + +void UniversalScanFilter::set_with_services(const EncodableList& value_arg) { + with_services_ = value_arg; +} + + +EncodableList UniversalScanFilter::ToEncodableList() const { + EncodableList list; + list.reserve(1); + list.push_back(EncodableValue(with_services_)); + return list; +} + +UniversalScanFilter UniversalScanFilter::FromEncodableList(const EncodableList& list) { + UniversalScanFilter decoded( + std::get(list[0])); + return decoded; +} + UniversalBlePlatformChannelCodecSerializer::UniversalBlePlatformChannelCodecSerializer() {} @@ -264,6 +311,8 @@ EncodableValue UniversalBlePlatformChannelCodecSerializer::ReadValueOfType( return CustomEncodableValue(UniversalBleScanResult::FromEncodableList(std::get(ReadValue(stream)))); case 130: return CustomEncodableValue(UniversalBleService::FromEncodableList(std::get(ReadValue(stream)))); + case 131: + return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get(ReadValue(stream)))); default: return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); } @@ -288,6 +337,11 @@ void UniversalBlePlatformChannelCodecSerializer::WriteValue( WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } + if (custom_value->type() == typeid(UniversalScanFilter)) { + stream->WriteByte(131); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } } flutter::StandardCodecSerializer::WriteValue(value, stream); } @@ -302,9 +356,9 @@ void UniversalBlePlatformChannel::SetUp( flutter::BinaryMessenger* binary_messenger, UniversalBlePlatformChannel* api) { { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { api->GetBluetoothAvailabilityState([reply](ErrorOr&& output) { if (output.has_error()) { @@ -320,13 +374,13 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { api->EnableBluetooth([reply](ErrorOr&& output) { if (output.has_error()) { @@ -342,15 +396,18 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { - std::optional output = api->StartScan(); + const auto& args = std::get(message); + const auto& encodable_filter_arg = args.at(0); + const auto* filter_arg = &(std::any_cast(std::get(encodable_filter_arg))); + std::optional output = api->StartScan(filter_arg); if (output.has_value()) { reply(WrapError(output.value())); return; @@ -363,13 +420,13 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { std::optional output = api->StopScan(); if (output.has_value()) { @@ -384,13 +441,13 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_device_id_arg = args.at(0); @@ -412,13 +469,13 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_device_id_arg = args.at(0); @@ -440,13 +497,13 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_device_id_arg = args.at(0); @@ -486,13 +543,13 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_device_id_arg = args.at(0); @@ -515,13 +572,13 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_device_id_arg = args.at(0); @@ -556,13 +613,13 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_device_id_arg = args.at(0); @@ -591,13 +648,13 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_device_id_arg = args.at(0); @@ -644,13 +701,13 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_device_id_arg = args.at(0); @@ -673,13 +730,13 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_device_id_arg = args.at(0); @@ -701,13 +758,13 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_device_id_arg = args.at(0); @@ -729,13 +786,13 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectedDevices", &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectedDevices", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_with_services_arg = args.at(0); @@ -758,7 +815,7 @@ void UniversalBlePlatformChannel::SetUp( } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } } @@ -819,11 +876,11 @@ void UniversalBleCallbackChannel::OnAvailabilityChanged( std::function&& on_success, std::function&& on_error) { const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged"; - auto channel = std::make_unique>(binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(state_arg), }); - channel->Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); const auto& encodable_return_value = *response; const auto* list_return_value = std::get_if(&encodable_return_value); @@ -846,13 +903,13 @@ void UniversalBleCallbackChannel::OnPairStateChange( std::function&& on_success, std::function&& on_error) { const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange"; - auto channel = std::make_unique>(binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(device_id_arg), EncodableValue(is_paired_arg), error_arg ? EncodableValue(*error_arg) : EncodableValue(), }); - channel->Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); const auto& encodable_return_value = *response; const auto* list_return_value = std::get_if(&encodable_return_value); @@ -873,11 +930,11 @@ void UniversalBleCallbackChannel::OnScanResult( std::function&& on_success, std::function&& on_error) { const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult"; - auto channel = std::make_unique>(binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ CustomEncodableValue(result_arg), }); - channel->Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); const auto& encodable_return_value = *response; const auto* list_return_value = std::get_if(&encodable_return_value); @@ -900,13 +957,13 @@ void UniversalBleCallbackChannel::OnValueChanged( std::function&& on_success, std::function&& on_error) { const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged"; - auto channel = std::make_unique>(binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(device_id_arg), EncodableValue(characteristic_id_arg), EncodableValue(value_arg), }); - channel->Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); const auto& encodable_return_value = *response; const auto* list_return_value = std::get_if(&encodable_return_value); @@ -928,12 +985,12 @@ void UniversalBleCallbackChannel::OnConnectionChanged( std::function&& on_success, std::function&& on_error) { const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged"; - auto channel = std::make_unique>(binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(device_id_arg), EncodableValue(state_arg), }); - channel->Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); const auto& encodable_return_value = *response; const auto* list_return_value = std::get_if(&encodable_return_value); diff --git a/windows/src/generated/universal_ble.g.h b/windows/src/generated/universal_ble.g.h index 1d4b0cc..2b0662b 100644 --- a/windows/src/generated/universal_ble.g.h +++ b/windows/src/generated/universal_ble.g.h @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v16.0.5), do not edit directly. +// Autogenerated from Pigeon (v17.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon #ifndef PIGEON_UNIVERSAL_BLE_G_H_ @@ -70,7 +70,8 @@ class UniversalBleScanResult { const bool* is_paired, const int64_t* rssi, const std::vector* manufacturer_data, - const std::vector* manufacturer_data_head); + const std::vector* manufacturer_data_head, + const flutter::EncodableList* services); const std::string& device_id() const; void set_device_id(std::string_view value_arg); @@ -95,6 +96,10 @@ class UniversalBleScanResult { void set_manufacturer_data_head(const std::vector* value_arg); void set_manufacturer_data_head(const std::vector& value_arg); + const flutter::EncodableList* services() const; + void set_services(const flutter::EncodableList* value_arg); + void set_services(const flutter::EncodableList& value_arg); + private: static UniversalBleScanResult FromEncodableList(const flutter::EncodableList& list); @@ -109,6 +114,7 @@ class UniversalBleScanResult { std::optional rssi_; std::optional> manufacturer_data_; std::optional> manufacturer_data_head_; + std::optional services_; }; @@ -172,6 +178,28 @@ class UniversalBleCharacteristic { }; + +// Generated class from Pigeon that represents data sent in messages. +class UniversalScanFilter { + public: + // Constructs an object setting all fields. + explicit UniversalScanFilter(const flutter::EncodableList& with_services); + + const flutter::EncodableList& with_services() const; + void set_with_services(const flutter::EncodableList& value_arg); + + + private: + static UniversalScanFilter FromEncodableList(const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class UniversalBlePlatformChannel; + friend class UniversalBlePlatformChannelCodecSerializer; + friend class UniversalBleCallbackChannel; + friend class UniversalBleCallbackChannelCodecSerializer; + flutter::EncodableList with_services_; + +}; + class UniversalBlePlatformChannelCodecSerializer : public flutter::StandardCodecSerializer { public: UniversalBlePlatformChannelCodecSerializer(); @@ -201,7 +229,7 @@ class UniversalBlePlatformChannel { virtual ~UniversalBlePlatformChannel() {} virtual void GetBluetoothAvailabilityState(std::function reply)> result) = 0; virtual void EnableBluetooth(std::function reply)> result) = 0; - virtual std::optional StartScan() = 0; + virtual std::optional StartScan(const UniversalScanFilter* filter) = 0; virtual std::optional StopScan() = 0; virtual std::optional Connect(const std::string& device_id) = 0; virtual std::optional Disconnect(const std::string& device_id) = 0; diff --git a/windows/src/universal_ble_plugin.cpp b/windows/src/universal_ble_plugin.cpp index 58c1752..24a0bf3 100644 --- a/windows/src/universal_ble_plugin.cpp +++ b/windows/src/universal_ble_plugin.cpp @@ -91,7 +91,7 @@ namespace universal_ble } }); } - std::optional UniversalBlePlugin::StartScan() + std::optional UniversalBlePlugin::StartScan(const UniversalScanFilter *filter) { if (bluetoothRadio && bluetoothRadio.State() == RadioState::On) { @@ -99,6 +99,8 @@ namespace universal_ble { bluetoothLEWatcher = BluetoothLEAdvertisementWatcher(); bluetoothLEWatcher.ScanningMode(BluetoothLEScanningMode::Active); + if (filter != nullptr) + ApplyScanFilter(filter, bluetoothLEWatcher); bluetoothLEWatcherReceivedToken = bluetoothLEWatcher.Received({this, &UniversalBlePlugin::BluetoothLEWatcher_Received}); } bluetoothLEWatcher.Start(); @@ -116,6 +118,20 @@ namespace universal_ble } }; + void UniversalBlePlugin::ApplyScanFilter(const UniversalScanFilter *filter, BluetoothLEAdvertisementWatcher &bluetoothWatcher) + { + // Apply Services filter + const auto &services = filter->with_services(); + if (!services.empty()) + { + for (const auto &uuid : services) + { + std::string uuid_str = std::get(uuid); + bluetoothWatcher.AdvertisementFilter().Advertisement().ServiceUuids().Append(uuid_to_guid(uuid_str)); + } + } + } + std::optional UniversalBlePlugin::StopScan() { @@ -516,6 +532,11 @@ namespace universal_ble scanResult.set_manufacturer_data_head(existingScanResult.manufacturer_data_head()); shouldUpdate = true; } + if (scanResult.services() == nullptr && existingScanResult.services() != nullptr) + { + scanResult.set_services(existingScanResult.services()); + shouldUpdate = true; + } // if nothing to update then return if (!shouldUpdate) @@ -629,6 +650,7 @@ namespace universal_ble int16_t rssi = rssiPropertyValue.GetInt16(); universalScanResult.set_rssi(rssi); } + pushUniversalScanResult(universalScanResult); } } @@ -668,6 +690,12 @@ namespace universal_ble universalScanResult.set_manufacturer_data_head(manufacturerData); universalScanResult.set_rssi(args.RawSignalStrengthInDBm()); + // Add services + flutter::EncodableList services = flutter::EncodableList(); + for (auto &&uuid : args.Advertisement().ServiceUuids()) + services.push_back(guid_to_uuid(uuid)); + universalScanResult.set_services(services); + // check if this device already discovered in deviceWatcher auto it = deviceWatcherDevices.find(deviceId); if (it != deviceWatcherDevices.end()) @@ -686,7 +714,6 @@ namespace universal_ble universalScanResult.set_name(winrt::to_string(deviceInfo.Name())); } - // Cache connectable status pushUniversalScanResult(universalScanResult); } catch (...) diff --git a/windows/src/universal_ble_plugin.h b/windows/src/universal_ble_plugin.h index 201558f..a3fa666 100644 --- a/windows/src/universal_ble_plugin.h +++ b/windows/src/universal_ble_plugin.h @@ -123,11 +123,12 @@ namespace universal_ble winrt::fire_and_forget WriteAsync(GattCharacteristic characteristic, GattWriteOption writeOption, const std::vector &value, std::function reply)> result); + void ApplyScanFilter(const UniversalScanFilter *filter, BluetoothLEAdvertisementWatcher &bluetoothWatcher); // UniversalBlePlatformChannel implementation. void GetBluetoothAvailabilityState(std::function reply)> result) override; void EnableBluetooth(std::function reply)> result) override; - std::optional StartScan() override; + std::optional StartScan(const UniversalScanFilter *filter) override; std::optional StopScan() override; std::optional Connect(const std::string &device_id) override; std::optional Disconnect(const std::string &device_id) override;