Implement readRSSI on Android and Darwin (#203)
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
## 1.1.0
|
||||
* Add readRssi method
|
||||
|
||||
## 1.0.1
|
||||
* Enforce C++20 standard for Windows builds
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -529,6 +529,7 @@ interface UniversalBlePlatformChannel {
|
||||
fun unPair(deviceId: String)
|
||||
fun getSystemDevices(withServices: List<String>, callback: (Result<List<UniversalBleScanResult>>) -> Unit)
|
||||
fun getConnectionState(deviceId: String): Long
|
||||
fun readRssi(deviceId: String, callback: (Result<Long>) -> Unit)
|
||||
fun setLogLevel(logLevel: UniversalBleLogLevel)
|
||||
|
||||
companion object {
|
||||
@@ -919,6 +920,26 @@ interface UniversalBlePlatformChannel {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readRssi$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val deviceIdArg = args[0] as String
|
||||
api.readRssi(deviceIdArg) { result: Result<Long> ->
|
||||
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<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
|
||||
@@ -344,3 +344,8 @@ class SubscriptionResultFuture(
|
||||
val serviceId: String,
|
||||
val result: (Result<Unit>) -> Unit,
|
||||
)
|
||||
|
||||
class RssiResultFuture(
|
||||
val deviceId: String,
|
||||
val result: (Result<Long>) -> Unit,
|
||||
)
|
||||
@@ -60,6 +60,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
private val writeResultFutureList = mutableListOf<WriteResultFuture>()
|
||||
private val subscriptionResultFutureList = mutableListOf<SubscriptionResultFuture>()
|
||||
private val pairResultFutures = mutableMapOf<String, (Result<Boolean>) -> Unit>()
|
||||
private val rssiResultFutureList = mutableListOf<RssiResultFuture>()
|
||||
|
||||
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<Long>) -> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Int64, Error>) -> 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
|
||||
|
||||
@@ -258,3 +258,13 @@ class DiscoverServicesFuture {
|
||||
self.result = result
|
||||
}
|
||||
}
|
||||
|
||||
class RssiReadFuture {
|
||||
let deviceId: String
|
||||
let result: (Result<Int64, Error>) -> Void
|
||||
|
||||
init(deviceId: String, result: @escaping (Result<Int64, Error>) -> Void) {
|
||||
self.deviceId = deviceId
|
||||
self.result = result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Int64, Error>) -> 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<Bool, Error>) -> 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 {
|
||||
|
||||
@@ -113,6 +113,12 @@ class MockUniversalBle extends UniversalBlePlatform {
|
||||
return 512;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> readRssi(String deviceId) async {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
return -50; // Mock RSSI value in dBm
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setNotifiable(String deviceId, String service,
|
||||
String characteristic, BleInputProperty bleInputProperty) async {}
|
||||
|
||||
@@ -1097,6 +1097,30 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
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
|
||||
|
||||
@@ -26,6 +26,19 @@ class BleDevice {
|
||||
Future<BleConnectionState> 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<int> 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 =>
|
||||
|
||||
@@ -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<int> 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.
|
||||
|
||||
@@ -378,6 +378,14 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> readRssi(String deviceId) async {
|
||||
throw UniversalBleException(
|
||||
code: UniversalBleErrorCode.notImplemented,
|
||||
message: "readRssi is not implemented on Linux platform",
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> pair(String deviceId) async {
|
||||
BlueZDevice device = _findDeviceById(deviceId);
|
||||
|
||||
@@ -1106,6 +1106,35 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> readRssi(String deviceId) async {
|
||||
final pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readRssi$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture =
|
||||
pigeonVar_channel.send(<Object?>[deviceId]);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
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<void> setLogLevel(UniversalBleLogLevel logLevel) async {
|
||||
final pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel$pigeonVar_messageChannelSuffix';
|
||||
|
||||
@@ -133,6 +133,10 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
|
||||
_executeWithErrorHandling(
|
||||
() => _channel.requestMtu(deviceId, expectedMtu));
|
||||
|
||||
@override
|
||||
Future<int> readRssi(String deviceId) =>
|
||||
_executeWithErrorHandling(() => _channel.readRssi(deviceId));
|
||||
|
||||
@override
|
||||
Future<bool> isPaired(String deviceId) =>
|
||||
_executeWithErrorHandling(() => _channel.isPaired(deviceId));
|
||||
|
||||
@@ -79,6 +79,8 @@ abstract class UniversalBlePlatform {
|
||||
|
||||
Future<int> requestMtu(String deviceId, int expectedMtu);
|
||||
|
||||
Future<int> readRssi(String deviceId);
|
||||
|
||||
Future<bool> isPaired(String deviceId);
|
||||
|
||||
Future<bool> pair(String deviceId);
|
||||
|
||||
@@ -283,6 +283,15 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Unimplemented`
|
||||
@override
|
||||
Future<int> readRssi(String deviceId) {
|
||||
throw UniversalBleException(
|
||||
code: UniversalBleErrorCode.notImplemented,
|
||||
message: "readRssi is not implemented on Web platform",
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isPaired(String deviceId) {
|
||||
throw UniversalBleException(
|
||||
|
||||
@@ -93,6 +93,9 @@ abstract class UniversalBlePlatformChannel {
|
||||
|
||||
int getConnectionState(String deviceId);
|
||||
|
||||
@async
|
||||
int readRssi(String deviceId);
|
||||
|
||||
void setLogLevel(UniversalBleLogLevel logLevel);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -132,4 +132,9 @@ class _UniversalBleMock extends UniversalBlePlatformMock {
|
||||
Future<void> requestPermissions({bool withAndroidFineLocation = false}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> readRssi(String deviceId) async {
|
||||
return -50; // Mock RSSI value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<EncodableValue>& reply) {
|
||||
try {
|
||||
const auto& args = std::get<EncodableList>(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<std::string>(encodable_device_id_arg);
|
||||
api->ReadRssi(device_id_arg, [reply](ErrorOr<int64_t>&& 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) {
|
||||
|
||||
@@ -435,6 +435,9 @@ class UniversalBlePlatformChannel {
|
||||
const flutter::EncodableList& with_services,
|
||||
std::function<void(ErrorOr<flutter::EncodableList> reply)> result) = 0;
|
||||
virtual ErrorOr<int64_t> GetConnectionState(const std::string& device_id) = 0;
|
||||
virtual void ReadRssi(
|
||||
const std::string& device_id,
|
||||
std::function<void(ErrorOr<int64_t> reply)> result) = 0;
|
||||
virtual std::optional<FlutterError> SetLogLevel(const UniversalBleLogLevel& log_level) = 0;
|
||||
|
||||
// The codec used by UniversalBlePlatformChannel.
|
||||
|
||||
@@ -451,6 +451,13 @@ void UniversalBlePlugin::RequestMtu(
|
||||
}
|
||||
}
|
||||
|
||||
void UniversalBlePlugin::ReadRssi(
|
||||
const std::string &device_id,
|
||||
std::function<void(ErrorOr<int64_t> 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<void(ErrorOr<bool> reply)> result) {
|
||||
|
||||
@@ -200,6 +200,8 @@ private:
|
||||
std::function<void(std::optional<FlutterError> reply)> result) override;
|
||||
void RequestMtu(const std::string &device_id, int64_t expected_mtu,
|
||||
std::function<void(ErrorOr<int64_t> reply)> result) override;
|
||||
void ReadRssi(const std::string &device_id,
|
||||
std::function<void(ErrorOr<int64_t> reply)> result) override;
|
||||
void IsPaired(const std::string &device_id,
|
||||
std::function<void(ErrorOr<bool> reply)> result) override;
|
||||
void Pair(const std::string &device_id,
|
||||
|
||||
Reference in New Issue
Block a user