diff --git a/CHANGELOG.md b/CHANGELOG.md index 3573155..787c929 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 1.1.0 +* Add readRssi method + ## 1.0.1 * Enforce C++20 standard for Windows builds diff --git a/README.low_level.md b/README.low_level.md index e475bd7..743eb26 100644 --- a/README.low_level.md +++ b/README.low_level.md @@ -131,3 +131,19 @@ This method will **attempt** to set the MTU (Maximum Transmission Unit) but it i ```dart int mtu = await UniversalBle.requestMtu(widget.deviceId, 247); ``` + +### Read RSSI + +Read the signal strength (RSSI) of a connected device. + +```dart +int rssi = await UniversalBle.readRssi(deviceId); +``` + +> ⚠️ Note: The device must be connected before reading RSSI. + +#### Platform Limitations + +* **Android / iOS / macOS**: Fully supported. + +* **Windows / Linux / Web**: Not supported. diff --git a/README.md b/README.md index a90a79e..343e495 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE - [Pairing](#pairing) - [Bluetooth Availability](#bluetooth-availability) - [Requesting MTU](#requesting-mtu) +- [Reading RSSI](#reading-rssi) - [Command Queue](#command-queue) - [Timeout](#timeout) - [Error Handling](#error-handling) @@ -52,6 +53,7 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE | enable/disable Bluetooth | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | | onAvailabilityChange | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | requestMtu | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | +| readRssi | ✔️ | ✔️ | ✔️ | ❌ | 🚧 | ❌ | | requestPermissions | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ## Getting Started @@ -409,7 +411,7 @@ UniversalBle.disableBluetooth(); ```dart int mtu = await bleDevice.requestMtu(256); -```` +``` > ⚠️ Note: Requesting an MTU is a *best-effort* operation. > On many platforms the final MTU is fully controlled by the OS and remote device. @@ -460,6 +462,23 @@ When developing cross-platform BLE applications and devices: * Take advantage of higher MTUs when available, without depending on them +### Reading RSSI + +Read the signal strength (RSSI) of a connected device. + +```dart +int rssi = await bleDevice.readRssi(); +``` + +> ⚠️ Note: The device must be connected before reading RSSI. + +#### Platform Limitations + +* **Android / iOS / macOS**: Fully supported. + +* **Windows / Linux / Web**: Not supported. + + ## Command Queue By default, all commands are executed in a global queue (`QueueType.global`), with each command waiting for the previous one to finish. While this method is slower it is the safest to avoid command exceptions and therefore is the default. 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 025e656..08e4e98 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 @@ -529,6 +529,7 @@ interface UniversalBlePlatformChannel { fun unPair(deviceId: String) fun getSystemDevices(withServices: List, callback: (Result>) -> Unit) fun getConnectionState(deviceId: String): Long + fun readRssi(deviceId: String, callback: (Result) -> Unit) fun setLogLevel(logLevel: UniversalBleLogLevel) companion object { @@ -919,6 +920,26 @@ interface UniversalBlePlatformChannel { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readRssi$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val deviceIdArg = args[0] as String + api.readRssi(deviceIdArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(UniversalBlePigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(UniversalBlePigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel$separatedMessageChannelSuffix", codec) if (api != null) { 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 cce83de..cf04228 100644 --- a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBleHelper.kt +++ b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBleHelper.kt @@ -343,4 +343,9 @@ class SubscriptionResultFuture( val characteristicId: String, val serviceId: String, val result: (Result) -> Unit, +) + +class RssiResultFuture( + val deviceId: String, + val result: (Result) -> Unit, ) \ No newline at end of file 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 59558d9..1682199 100644 --- a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePlugin.kt +++ b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePlugin.kt @@ -60,6 +60,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), private val writeResultFutureList = mutableListOf() private val subscriptionResultFutureList = mutableListOf() private val pairResultFutures = mutableMapOf) -> Unit>() + private val rssiResultFutureList = mutableListOf() override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { UniversalBlePlatformChannel.setUp(flutterPluginBinding.binaryMessenger, this) @@ -279,6 +280,49 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), UniversalBleLogger.setLogLevel(logLevel) } + override fun readRssi(deviceId: String, callback: (Result) -> Unit) { + try { + val gatt = deviceId.toBluetoothGatt() + if (gatt.readRemoteRssi()) { + rssiResultFutureList.add(RssiResultFuture(deviceId, callback)) + } else { + callback( + Result.failure( + createFlutterError( + UniversalBleErrorCode.FAILED, + "Failed to read RSSI" + ) + ) + ) + } + } catch (e: FlutterError) { + callback(Result.failure(e)) + } + } + + override fun onReadRemoteRssi(gatt: BluetoothGatt?, rssi: Int, status: Int) { + val deviceId = gatt?.device?.address ?: return + rssiResultFutureList.removeAll { + if (it.deviceId == deviceId) { + if (status == BluetoothGatt.GATT_SUCCESS) { + it.result(Result.success(rssi.toLong())) + } else { + it.result( + Result.failure( + createFlutterError( + UniversalBleErrorCode.FAILED, + "Failed to read RSSI" + ) + ) + ) + } + true + } else { + false + } + } + } + override fun discoverServices( deviceId: String, withDescriptors: Boolean, @@ -311,36 +355,44 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { if (status != BluetoothGatt.GATT_SUCCESS) { - discoverServicesFutureList.filter { it.deviceId == gatt.device.address }.forEach { - discoverServicesFutureList.remove(it) - it.result( - Result.failure( - createFlutterError( - UniversalBleErrorCode.FAILED, - "Failed to discover services" + discoverServicesFutureList.removeAll { + if (it.deviceId == gatt.device.address) { + it.result( + Result.failure( + createFlutterError( + UniversalBleErrorCode.FAILED, + "Failed to discover services" + ) ) ) - ) + true + } else { + false + } } return } setCachedServices(gatt.device.address, gatt.services.map { it.uuid.toString() }) - discoverServicesFutureList.filter { it.deviceId == gatt.device.address }.forEach { - discoverServicesFutureList.remove(it) - it.result(Result.success(gatt.services.map { service -> - UniversalBleService( - uuid = service.uuid.toString(), - characteristics = service.characteristics.map { char -> - UniversalBleCharacteristic( - uuid = char.uuid.toString(), - properties = char.getPropertiesList(), - descriptors = if (it.withDescriptors) char.descriptors.map { descriptor -> - UniversalBleDescriptor(descriptor.uuid.toString()) - } else listOf() - ) - } - ) - })) + discoverServicesFutureList.removeAll { + if (it.deviceId == gatt.device.address) { + it.result(Result.success(gatt.services.map { service -> + UniversalBleService( + uuid = service.uuid.toString(), + characteristics = service.characteristics.map { char -> + UniversalBleCharacteristic( + uuid = char.uuid.toString(), + properties = char.getPropertiesList(), + descriptors = if (it.withDescriptors) char.descriptors.map { descriptor -> + UniversalBleDescriptor(descriptor.uuid.toString()) + } else listOf() + ) + } + ) + })) + true + } else { + false + } } } @@ -517,29 +569,31 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), value: ByteArray, status: Int, ) { - readResultFutureList.filter { - it.deviceId == gatt.device.address && - it.characteristicId == characteristic.uuid.toString() && - it.serviceId == characteristic.service.uuid.toString() - }.forEach { - readResultFutureList.remove(it) - if (status == BluetoothGatt.GATT_SUCCESS) { - it.result(Result.success(value)) - } else { - UniversalBleLogger.logError( - "READ_FAILED <- ${gatt.device.address} ${characteristic.uuid} status=$status" - ) - it.result( - Result.failure( - createFlutterError( - gattStatusToUniversalBleErrorCode(status), - "Failed to read", - status.toString() + readResultFutureList.removeAll { + if (it.deviceId == gatt.device.address && + it.characteristicId == characteristic.uuid.toString() && + it.serviceId == characteristic.service.uuid.toString() + ) { + if (status == BluetoothGatt.GATT_SUCCESS) { + it.result(Result.success(value)) + } else { + UniversalBleLogger.logError( + "READ_FAILED <- ${gatt.device.address} ${characteristic.uuid} status=$status" + ) + it.result( + Result.failure( + createFlutterError( + gattStatusToUniversalBleErrorCode(status), + "Failed to read", + status.toString() + ) ) ) - ) + } + true + } else { + false } - } } @@ -640,27 +694,30 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), characteristic: BluetoothGattCharacteristic, status: Int, ) { - writeResultFutureList.filter { - it.deviceId == gatt?.device?.address && - it.characteristicId == characteristic.uuid.toString() && - it.serviceId == characteristic.service.uuid.toString() - }.forEach { - writeResultFutureList.remove(it) - if (status == BluetoothGatt.GATT_SUCCESS) { - it.result(Result.success(Unit)) - } else { - UniversalBleLogger.logError( - "WRITE_FAILED <- ${gatt?.device?.address} ${characteristic.uuid} status=$status" - ) - it.result( - Result.failure( - createFlutterError( - gattStatusToUniversalBleErrorCode(status), - "Failed to write", - status.toString() + writeResultFutureList.removeAll { + if (it.deviceId == gatt?.device?.address && + it.characteristicId == characteristic.uuid.toString() && + it.serviceId == characteristic.service.uuid.toString() + ) { + if (status == BluetoothGatt.GATT_SUCCESS) { + it.result(Result.success(Unit)) + } else { + UniversalBleLogger.logError( + "WRITE_FAILED <- ${gatt?.device?.address} ${characteristic.uuid} status=$status" + ) + it.result( + Result.failure( + createFlutterError( + gattStatusToUniversalBleErrorCode(status), + "Failed to write", + status.toString() + ) ) ) - ) + } + true + } else { + false } } } @@ -679,19 +736,23 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) { val deviceId = gatt?.device?.address ?: return - mtuResultFutureList.filter { it.deviceId == deviceId }.forEach { - mtuResultFutureList.remove(it) - if (status == BluetoothGatt.GATT_SUCCESS) { - it.result(Result.success(mtu.toLong())) - } else { - it.result( - Result.failure( - createFlutterError( - UniversalBleErrorCode.FAILED, - "Failed to change MTU" + mtuResultFutureList.removeAll { + if (it.deviceId == deviceId) { + if (status == BluetoothGatt.GATT_SUCCESS) { + it.result(Result.success(mtu.toLong())) + } else { + it.result( + Result.failure( + createFlutterError( + UniversalBleErrorCode.FAILED, + "Failed to change MTU" + ) ) ) - ) + } + true + } else { + false } } } @@ -941,6 +1002,14 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), false } } + rssiResultFutureList.removeAll { + if (it.deviceId == gatt.device.address) { + it.result(Result.failure(deviceDisconnectedError)) + true + } else { + false + } + } } private fun onBondStateUpdate(deviceId: String, bonded: Boolean, error: String? = null) { @@ -1116,24 +1185,27 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), service: String, status: Int, ) { - subscriptionResultFutureList.filter { - it.deviceId == deviceId && - it.characteristicId == characteristic && - it.serviceId == service - }.forEach { - subscriptionResultFutureList.remove(it) - if (status != BluetoothGatt.GATT_SUCCESS) { - it.result( - Result.failure( - createFlutterError( - gattStatusToUniversalBleErrorCode(status), - "Failed to update subscription state", - status.toString() + subscriptionResultFutureList.removeAll { + if (it.deviceId == deviceId && + it.characteristicId == characteristic && + it.serviceId == service + ) { + if (status != BluetoothGatt.GATT_SUCCESS) { + it.result( + Result.failure( + createFlutterError( + gattStatusToUniversalBleErrorCode(status), + "Failed to update subscription state", + status.toString() + ) ) ) - ) + } else { + it.result(Result.success(Unit)) + } + true } else { - it.result(Result.success(Unit)) + false } } } diff --git a/darwin/Classes/UniversalBle.g.swift b/darwin/Classes/UniversalBle.g.swift index af31208..bfeb176 100644 --- a/darwin/Classes/UniversalBle.g.swift +++ b/darwin/Classes/UniversalBle.g.swift @@ -548,6 +548,7 @@ protocol UniversalBlePlatformChannel { func unPair(deviceId: String) throws func getSystemDevices(withServices: [String], completion: @escaping (Result<[UniversalBleScanResult], Error>) -> Void) func getConnectionState(deviceId: String) throws -> Int64 + func readRssi(deviceId: String, completion: @escaping (Result) -> Void) func setLogLevel(logLevel: UniversalBleLogLevel) throws } @@ -882,6 +883,23 @@ class UniversalBlePlatformChannelSetup { } else { getConnectionStateChannel.setMessageHandler(nil) } + let readRssiChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readRssi\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + readRssiChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let deviceIdArg = args[0] as! String + api.readRssi(deviceId: deviceIdArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + readRssiChannel.setMessageHandler(nil) + } let setLogLevelChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { setLogLevelChannel.setMessageHandler { message, reply in diff --git a/darwin/Classes/UniversalBleHelper.swift b/darwin/Classes/UniversalBleHelper.swift index ade3600..425ecd0 100644 --- a/darwin/Classes/UniversalBleHelper.swift +++ b/darwin/Classes/UniversalBleHelper.swift @@ -258,3 +258,13 @@ class DiscoverServicesFuture { self.result = result } } + +class RssiReadFuture { + let deviceId: String + let result: (Result) -> Void + + init(deviceId: String, result: @escaping (Result) -> Void) { + self.deviceId = deviceId + self.result = result + } +} diff --git a/darwin/Classes/UniversalBlePlugin.swift b/darwin/Classes/UniversalBlePlugin.swift index d03bd81..b6cf0a2 100644 --- a/darwin/Classes/UniversalBlePlugin.swift +++ b/darwin/Classes/UniversalBlePlugin.swift @@ -40,6 +40,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral private var characteristicWriteWithoutResponseFutures = [CharacteristicWriteFuture]() private var characteristicNotifyFutures = [CharacteristicNotifyFuture]() private var discoverServicesFutures = [DiscoverServicesFuture]() + private var rssiReadFutures = [RssiReadFuture]() private var isManageScanning = false init(callbackChannel: UniversalBleCallbackChannel) { @@ -200,6 +201,15 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral } return false } + rssiReadFutures.removeAll { future in + if future.deviceId == deviceId { + future.result( + Result.failure(createFlutterError(code: .deviceDisconnected, message: "Device Disconnected")) + ) + return true + } + return false + } activeServiceDiscoveries[deviceId]?.cleanup() activeServiceDiscoveries[deviceId] = nil } @@ -334,6 +344,16 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral completion(Result.success(mtuResult)) } + func readRssi(deviceId: String, completion: @escaping (Result) -> Void) { + UniversalBleLogger.shared.logDebug("READ_RSSI -> \(deviceId)") + guard let peripheral = deviceId.findPeripheral(manager: manager) else { + completion(Result.failure(createFlutterError(code: .deviceNotFound, message: "Unknown deviceId:\(deviceId)"))) + return + } + peripheral.readRSSI() + rssiReadFutures.append(RssiReadFuture(deviceId: deviceId, result: completion)) + } + func isPaired(deviceId _: String, completion: @escaping (Result) -> Void) { completion(Result.failure(createFlutterError(code: .notSupported))) } @@ -531,6 +551,21 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral return false } } + + public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) { + rssiReadFutures.removeAll { future in + if future.deviceId == peripheral.uuid.uuidString { + if let flutterError = error?.toFlutterError() { + UniversalBleLogger.shared.logError("READ_RSSI_FAILED <- \(peripheral.uuid.uuidString): \(flutterError.message ?? "")") + future.result(Result.failure(flutterError)) + } else { + future.result(Result.success(RSSI.int64Value)) + } + return true + } + return false + } + } } extension CBPeripheral { diff --git a/example/lib/data/mock_universal_ble.dart b/example/lib/data/mock_universal_ble.dart index 9539f6a..076f81f 100644 --- a/example/lib/data/mock_universal_ble.dart +++ b/example/lib/data/mock_universal_ble.dart @@ -113,6 +113,12 @@ class MockUniversalBle extends UniversalBlePlatform { return 512; } + @override + Future readRssi(String deviceId) async { + await Future.delayed(const Duration(milliseconds: 500)); + return -50; // Mock RSSI value in dBm + } + @override Future setNotifiable(String deviceId, String service, String characteristic, BleInputProperty bleInputProperty) async {} diff --git a/example/lib/peripheral_details/peripheral_detail_page.dart b/example/lib/peripheral_details/peripheral_detail_page.dart index 105cb68..4bd056a 100644 --- a/example/lib/peripheral_details/peripheral_detail_page.dart +++ b/example/lib/peripheral_details/peripheral_detail_page.dart @@ -1097,6 +1097,30 @@ class _PeripheralDetailPageState extends State { ), ), ), + OutlinedButton.icon( + onPressed: isConnected + ? () async { + try { + int rssi = await bleDevice.readRssi(); + _addLog('RSSI', '$rssi dBm'); + } catch (e) { + _addLog('ReadRssiError (${e.runtimeType})', e); + } + } + : null, + icon: const Icon(Icons.signal_cellular_alt), + label: const Text('Get RSSI'), + style: OutlinedButton.styleFrom( + foregroundColor: colorScheme.onSurface, + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), if (BleCapabilities.supportsRequestMtuApi) ElevatedButton.icon( onPressed: isConnected diff --git a/lib/src/models/ble_device.dart b/lib/src/models/ble_device.dart index c6640bf..4488be2 100644 --- a/lib/src/models/ble_device.dart +++ b/lib/src/models/ble_device.dart @@ -26,6 +26,19 @@ class BleDevice { Future get connectionState => UniversalBle.getConnectionState(deviceId); + /// Read the RSSI value of this connected device. + /// + /// Returns the current RSSI value in dBm. This value indicates the signal strength + /// between the device and the connected peripheral. Lower (more negative) values + /// indicate weaker signal, while higher (less negative) values indicate stronger signal. + /// + /// **Note**: The device must be connected before reading RSSI. + /// + /// Throws [BleException] if: + /// - The device is not connected + /// - Reading RSSI fails + Future readRssi() => UniversalBle.readRssi(deviceId); + /// On web, it returns true if the web browser supports receiving advertisements from this device. /// The rest of the platforms will always return true. bool get receivesAdvertisements => diff --git a/lib/src/universal_ble.dart b/lib/src/universal_ble.dart index f485841..6a65ee8 100644 --- a/lib/src/universal_ble.dart +++ b/lib/src/universal_ble.dart @@ -340,6 +340,28 @@ class UniversalBle { ); } + /// Read the RSSI value of a connected device. + /// + /// Returns the current RSSI value in dBm. This value indicates the signal strength + /// between the device and the connected peripheral. Lower (more negative) values + /// indicate weaker signal, while higher (less negative) values indicate stronger signal. + /// + /// **Note**: The device must be connected before reading RSSI. + /// + /// Throws [BleException] if: + /// - The device is not connected + /// - Reading RSSI fails + static Future readRssi( + String deviceId, { + Duration? timeout, + }) async { + return await _bleCommandQueue.queueCommand( + () => _platform.readRssi(deviceId), + timeout: timeout, + deviceId: deviceId, + ); + } + /// Check if a device is paired. /// /// For `Apple` and `Web`, you have to pass a "pairingCommand" with an encrypted read or write characteristic. diff --git a/lib/src/universal_ble_linux/universal_ble_linux.dart b/lib/src/universal_ble_linux/universal_ble_linux.dart index 05206a9..6035dcf 100644 --- a/lib/src/universal_ble_linux/universal_ble_linux.dart +++ b/lib/src/universal_ble_linux/universal_ble_linux.dart @@ -378,6 +378,14 @@ class UniversalBleLinux extends UniversalBlePlatform { ); } + @override + Future readRssi(String deviceId) async { + throw UniversalBleException( + code: UniversalBleErrorCode.notImplemented, + message: "readRssi is not implemented on Linux platform", + ); + } + @override Future pair(String deviceId) async { BlueZDevice device = _findDeviceById(deviceId); diff --git a/lib/src/universal_ble_pigeon/universal_ble.g.dart b/lib/src/universal_ble_pigeon/universal_ble.g.dart index edc655d..fd17426 100644 --- a/lib/src/universal_ble_pigeon/universal_ble.g.dart +++ b/lib/src/universal_ble_pigeon/universal_ble.g.dart @@ -1106,6 +1106,35 @@ class UniversalBlePlatformChannel { } } + Future readRssi(String deviceId) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readRssi$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([deviceId]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as int?)!; + } + } + Future setLogLevel(UniversalBleLogLevel logLevel) async { final pigeonVar_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel$pigeonVar_messageChannelSuffix'; 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 4c80478..5029646 100644 --- a/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart +++ b/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart @@ -133,6 +133,10 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { _executeWithErrorHandling( () => _channel.requestMtu(deviceId, expectedMtu)); + @override + Future readRssi(String deviceId) => + _executeWithErrorHandling(() => _channel.readRssi(deviceId)); + @override Future isPaired(String deviceId) => _executeWithErrorHandling(() => _channel.isPaired(deviceId)); diff --git a/lib/src/universal_ble_platform_interface.dart b/lib/src/universal_ble_platform_interface.dart index 1bd5f02..68f930e 100644 --- a/lib/src/universal_ble_platform_interface.dart +++ b/lib/src/universal_ble_platform_interface.dart @@ -79,6 +79,8 @@ abstract class UniversalBlePlatform { Future requestMtu(String deviceId, int expectedMtu); + Future readRssi(String deviceId); + Future isPaired(String deviceId); Future pair(String deviceId); diff --git a/lib/src/universal_ble_web/universal_ble_web.dart b/lib/src/universal_ble_web/universal_ble_web.dart index 2cf2b28..73c4bb8 100644 --- a/lib/src/universal_ble_web/universal_ble_web.dart +++ b/lib/src/universal_ble_web/universal_ble_web.dart @@ -283,6 +283,15 @@ class UniversalBleWeb extends UniversalBlePlatform { ); } + /// `Unimplemented` + @override + Future readRssi(String deviceId) { + throw UniversalBleException( + code: UniversalBleErrorCode.notImplemented, + message: "readRssi is not implemented on Web platform", + ); + } + @override Future isPaired(String deviceId) { throw UniversalBleException( diff --git a/pigeon/universal_ble.dart b/pigeon/universal_ble.dart index ab491c3..9d389e6 100644 --- a/pigeon/universal_ble.dart +++ b/pigeon/universal_ble.dart @@ -93,6 +93,9 @@ abstract class UniversalBlePlatformChannel { int getConnectionState(String deviceId); + @async + int readRssi(String deviceId); + void setLogLevel(UniversalBleLogLevel logLevel); } diff --git a/pubspec.yaml b/pubspec.yaml index d6cb2ff..a6ac4be 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: 1.0.1+3 +version: 1.1.0 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 index 70ddc87..409c855 100644 --- a/test/ble_characteristic_test.dart +++ b/test/ble_characteristic_test.dart @@ -132,4 +132,9 @@ class _UniversalBleMock extends UniversalBlePlatformMock { Future requestPermissions({bool withAndroidFineLocation = false}) { throw UnimplementedError(); } + + @override + Future readRssi(String deviceId) async { + return -50; // Mock RSSI value + } } diff --git a/windows/src/generated/universal_ble.g.cpp b/windows/src/generated/universal_ble.g.cpp index c53a60d..40f6e3c 100644 --- a/windows/src/generated/universal_ble.g.cpp +++ b/windows/src/generated/universal_ble.g.cpp @@ -1189,6 +1189,35 @@ void UniversalBlePlatformChannel::SetUp( channel.SetMessageHandler(nullptr); } } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readRssi" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + 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); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); + return; + } + const auto& device_id_arg = std::get(encodable_device_id_arg); + api->ReadRssi(device_id_arg, [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } { BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel" + prepended_suffix, &GetCodec()); if (api != nullptr) { diff --git a/windows/src/generated/universal_ble.g.h b/windows/src/generated/universal_ble.g.h index ac593d4..92e5fdb 100644 --- a/windows/src/generated/universal_ble.g.h +++ b/windows/src/generated/universal_ble.g.h @@ -435,6 +435,9 @@ class UniversalBlePlatformChannel { const flutter::EncodableList& with_services, std::function reply)> result) = 0; virtual ErrorOr GetConnectionState(const std::string& device_id) = 0; + virtual void ReadRssi( + const std::string& device_id, + std::function reply)> result) = 0; virtual std::optional SetLogLevel(const UniversalBleLogLevel& log_level) = 0; // The codec used by UniversalBlePlatformChannel. diff --git a/windows/src/universal_ble_plugin.cpp b/windows/src/universal_ble_plugin.cpp index 5466944..523b9d8 100644 --- a/windows/src/universal_ble_plugin.cpp +++ b/windows/src/universal_ble_plugin.cpp @@ -451,6 +451,13 @@ void UniversalBlePlugin::RequestMtu( } } +void UniversalBlePlugin::ReadRssi( + const std::string &device_id, + std::function reply)> result) { + result(create_flutter_error(UniversalBleErrorCode::kNotImplemented, + "readRssi is not implemented on Windows platform")); +} + void UniversalBlePlugin::IsPaired( const std::string &device_id, std::function reply)> result) { diff --git a/windows/src/universal_ble_plugin.h b/windows/src/universal_ble_plugin.h index 6045582..1e0ac42 100644 --- a/windows/src/universal_ble_plugin.h +++ b/windows/src/universal_ble_plugin.h @@ -200,6 +200,8 @@ private: std::function reply)> result) override; void RequestMtu(const std::string &device_id, int64_t expected_mtu, std::function reply)> result) override; + void ReadRssi(const std::string &device_id, + std::function reply)> result) override; void IsPaired(const std::string &device_id, std::function reply)> result) override; void Pair(const std::string &device_id,