diff --git a/example/lib/main.dart b/example/lib/main.dart index cfc98927..82e028f0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -29,6 +29,7 @@ void main() { return _ble.getDiscoveredServices(deviceId); }, logMessage: _bleLogger.addToLog, + readRssi: _ble.readRssi, ); runApp( MultiProvider( diff --git a/example/lib/src/ble/ble_device_interactor.dart b/example/lib/src/ble/ble_device_interactor.dart index 7cee9511..5be0a789 100644 --- a/example/lib/src/ble/ble_device_interactor.dart +++ b/example/lib/src/ble/ble_device_interactor.dart @@ -6,11 +6,14 @@ class BleDeviceInteractor { BleDeviceInteractor({ required Future> Function(String deviceId) bleDiscoverServices, required void Function(String message) logMessage, + required this.readRssi, }) : _bleDiscoverServices = bleDiscoverServices, _logMessage = logMessage; final Future> Function(String deviceId) _bleDiscoverServices; + final Future Function(String deviceId) readRssi; + final void Function(String message) _logMessage; Future> discoverServices(String deviceId) async { diff --git a/example/lib/src/ui/device_detail/device_interaction_tab.dart b/example/lib/src/ui/device_detail/device_interaction_tab.dart index baf7ee3c..02a2fb98 100644 --- a/example/lib/src/ui/device_detail/device_interaction_tab.dart +++ b/example/lib/src/ui/device_detail/device_interaction_tab.dart @@ -28,6 +28,7 @@ class DeviceInteractionTab extends StatelessWidget { connectionStatus: connectionStateUpdate.connectionState, deviceConnector: deviceConnector, discoverServices: () => serviceDiscoverer.discoverServices(device.id), + readRssi: () => serviceDiscoverer.readRssi(device.id), ), ), ); @@ -42,12 +43,14 @@ class DeviceInteractionViewModel extends $DeviceInteractionViewModel { required this.connectionStatus, required this.deviceConnector, required this.discoverServices, + required this.readRssi, }); final String deviceId; final Connectable connectableStatus; final DeviceConnectionState connectionStatus; final BleDeviceConnector deviceConnector; + final Future Function() readRssi; @CustomEquality(Ignore()) final Future> Function() discoverServices; @@ -78,6 +81,8 @@ class _DeviceInteractionTab extends StatefulWidget { class _DeviceInteractionTabState extends State<_DeviceInteractionTab> { late List discoveredServices; + int _rssi = 0; + @override void initState() { discoveredServices = []; @@ -91,6 +96,13 @@ class _DeviceInteractionTabState extends State<_DeviceInteractionTab> { }); } + Future readRssi() async { + final rssi = await widget.viewModel.readRssi(); + setState(() { + _rssi = rssi; + }); + } + @override Widget build(BuildContext context) => CustomScrollView( slivers: [ @@ -118,10 +130,17 @@ class _DeviceInteractionTabState extends State<_DeviceInteractionTab> { style: const TextStyle(fontWeight: FontWeight.bold), ), ), + Padding( + padding: const EdgeInsetsDirectional.only(start: 16.0), + child: Text( + "Rssi: $_rssi dB", + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), Padding( padding: const EdgeInsets.only(top: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + child: Wrap( + alignment: WrapAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: !widget.viewModel.deviceConnected ? widget.viewModel.connect : null, @@ -135,6 +154,12 @@ class _DeviceInteractionTabState extends State<_DeviceInteractionTab> { onPressed: widget.viewModel.deviceConnected ? discoverServices : null, child: const Text("Discover Services"), ), + ElevatedButton( + onPressed: widget.viewModel.deviceConnected + ? readRssi + : null, + child: const Text("Get RSSI"), + ), ], ), ), diff --git a/example/lib/src/ui/device_detail/device_interaction_tab.g.dart b/example/lib/src/ui/device_detail/device_interaction_tab.g.dart index d59cd306..b4f78b7c 100644 --- a/example/lib/src/ui/device_detail/device_interaction_tab.g.dart +++ b/example/lib/src/ui/device_detail/device_interaction_tab.g.dart @@ -13,6 +13,7 @@ abstract class $DeviceInteractionViewModel { Connectable get connectableStatus; DeviceConnectionState get connectionStatus; BleDeviceConnector get deviceConnector; + Future Function() get readRssi; Future> Function() get discoverServices; DeviceInteractionViewModel copyWith({ @@ -20,6 +21,7 @@ abstract class $DeviceInteractionViewModel { Connectable? connectableStatus, DeviceConnectionState? connectionStatus, BleDeviceConnector? deviceConnector, + Future Function()? readRssi, Future> Function()? discoverServices, }) => DeviceInteractionViewModel( @@ -27,6 +29,7 @@ abstract class $DeviceInteractionViewModel { connectableStatus: connectableStatus ?? this.connectableStatus, connectionStatus: connectionStatus ?? this.connectionStatus, deviceConnector: deviceConnector ?? this.deviceConnector, + readRssi: readRssi ?? this.readRssi, discoverServices: discoverServices ?? this.discoverServices, ); @@ -37,6 +40,7 @@ abstract class $DeviceInteractionViewModel { this.connectableStatus, this.connectionStatus, this.deviceConnector, + this.readRssi, this.discoverServices, ); mutator(change); @@ -45,13 +49,14 @@ abstract class $DeviceInteractionViewModel { connectableStatus: change.connectableStatus, connectionStatus: change.connectionStatus, deviceConnector: change.deviceConnector, + readRssi: change.readRssi, discoverServices: change.discoverServices, ); } @override String toString() => - "DeviceInteractionViewModel(deviceId: $deviceId, connectableStatus: $connectableStatus, connectionStatus: $connectionStatus, deviceConnector: $deviceConnector, discoverServices: $discoverServices)"; + "DeviceInteractionViewModel(deviceId: $deviceId, connectableStatus: $connectableStatus, connectionStatus: $connectionStatus, deviceConnector: $deviceConnector, readRssi: $readRssi, discoverServices: $discoverServices)"; @override // ignore: avoid_equals_and_hash_code_on_mutable_classes @@ -62,6 +67,7 @@ abstract class $DeviceInteractionViewModel { connectableStatus == other.connectableStatus && connectionStatus == other.connectionStatus && deviceConnector == other.deviceConnector && + readRssi == other.readRssi && const Ignore().equals(discoverServices, other.discoverServices); @override @@ -72,6 +78,7 @@ abstract class $DeviceInteractionViewModel { result = 37 * result + connectableStatus.hashCode; result = 37 * result + connectionStatus.hashCode; result = 37 * result + deviceConnector.hashCode; + result = 37 * result + readRssi.hashCode; result = 37 * result + const Ignore().hash(discoverServices); return result; } @@ -83,6 +90,7 @@ class DeviceInteractionViewModel$Change { this.connectableStatus, this.connectionStatus, this.deviceConnector, + this.readRssi, this.discoverServices, ); @@ -90,6 +98,7 @@ class DeviceInteractionViewModel$Change { Connectable connectableStatus; DeviceConnectionState connectionStatus; BleDeviceConnector deviceConnector; + Future Function() readRssi; Future> Function() discoverServices; } @@ -124,8 +133,15 @@ class DeviceInteractionViewModel$ { deviceConnectorContainer.copyWith(deviceConnector: deviceConnector), ); - static final discoverServices = Lens> Function()>( + static final readRssi = + Lens Function()>( + (readRssiContainer) => readRssiContainer.readRssi, + (readRssiContainer, readRssi) => + readRssiContainer.copyWith(readRssi: readRssi), + ); + + static final discoverServices = + Lens> Function()>( (discoverServicesContainer) => discoverServicesContainer.discoverServices, (discoverServicesContainer, discoverServices) => discoverServicesContainer.copyWith(discoverServices: discoverServices), diff --git a/packages/flutter_reactive_ble/lib/src/reactive_ble.dart b/packages/flutter_reactive_ble/lib/src/reactive_ble.dart index 2142559b..94f1188c 100644 --- a/packages/flutter_reactive_ble/lib/src/reactive_ble.dart +++ b/packages/flutter_reactive_ble/lib/src/reactive_ble.dart @@ -398,6 +398,12 @@ class FlutterReactiveBle { Future clearGattCache(String deviceId) => _blePlatform.clearGattCache(deviceId).then((info) => info.dematerialize()); + /// Reads the RSSI of the of the peripheral with the given device ID. + /// The peripheral must be connected, otherwise a [PlatformException] will be + /// thrown + Future readRssi(String deviceId) async => + _blePlatform.readRssi(deviceId); + /// Subscribes to updates from the characteristic specified. /// /// This stream terminates automatically when the device is disconnected. diff --git a/packages/flutter_reactive_ble/test/connected_device_operation_test.mocks.dart b/packages/flutter_reactive_ble/test/connected_device_operation_test.mocks.dart index c98f086a..fe85af06 100644 --- a/packages/flutter_reactive_ble/test/connected_device_operation_test.mocks.dart +++ b/packages/flutter_reactive_ble/test/connected_device_operation_test.mocks.dart @@ -143,6 +143,14 @@ class MockReactiveBlePlatform extends _i1.Mock _i2.Result<_i2.Unit, _i2.GenericFailure<_i2.ClearGattCacheError>?>>); @override + _i4.Future readRssi(String? deviceId) => (super.noSuchMethod( + Invocation.method( + #readRssi, + [deviceId], + ), + returnValue: _i4.Future.value(0), + ) as _i4.Future); + @override _i4.Stream connectToDevice( String? id, Map<_i2.Uuid, List<_i2.Uuid>>? servicesWithCharacteristicsToDiscover, diff --git a/packages/flutter_reactive_ble/test/device_connector_test.mocks.dart b/packages/flutter_reactive_ble/test/device_connector_test.mocks.dart index 58ace777..998f5d96 100644 --- a/packages/flutter_reactive_ble/test/device_connector_test.mocks.dart +++ b/packages/flutter_reactive_ble/test/device_connector_test.mocks.dart @@ -155,6 +155,14 @@ class MockReactiveBlePlatform extends _i1.Mock _i2.Result<_i2.Unit, _i2.GenericFailure<_i2.ClearGattCacheError>?>>); @override + _i4.Future readRssi(String? deviceId) => (super.noSuchMethod( + Invocation.method( + #readRssi, + [deviceId], + ), + returnValue: _i4.Future.value(0), + ) as _i4.Future); + @override _i4.Stream connectToDevice( String? id, Map<_i2.Uuid, List<_i2.Uuid>>? servicesWithCharacteristicsToDiscover, diff --git a/packages/flutter_reactive_ble/test/device_scanner_test.mocks.dart b/packages/flutter_reactive_ble/test/device_scanner_test.mocks.dart index b52b7699..baee27ad 100644 --- a/packages/flutter_reactive_ble/test/device_scanner_test.mocks.dart +++ b/packages/flutter_reactive_ble/test/device_scanner_test.mocks.dart @@ -143,6 +143,14 @@ class MockReactiveBlePlatform extends _i1.Mock _i2.Result<_i2.Unit, _i2.GenericFailure<_i2.ClearGattCacheError>?>>); @override + _i4.Future readRssi(String? deviceId) => (super.noSuchMethod( + Invocation.method( + #readRssi, + [deviceId], + ), + returnValue: _i4.Future.value(0), + ) as _i4.Future); + @override _i4.Stream connectToDevice( String? id, Map<_i2.Uuid, List<_i2.Uuid>>? servicesWithCharacteristicsToDiscover, diff --git a/packages/flutter_reactive_ble/test/reactive_ble_test.dart b/packages/flutter_reactive_ble/test/reactive_ble_test.dart index 9f4ebb79..1f65fee5 100644 --- a/packages/flutter_reactive_ble/test/reactive_ble_test.dart +++ b/packages/flutter_reactive_ble/test/reactive_ble_test.dart @@ -486,6 +486,14 @@ void main() { }); }); + test('Read RSSI', () async { + const deviceId = '123'; + + when(_blePlatform.readRssi(deviceId)).thenAnswer((_) async => -42); + + expect(await _sut.readRssi(deviceId), -42); + }); + group('ConnecteddeviceStream stream', () { const update = ConnectionStateUpdate( deviceId: '123', diff --git a/packages/flutter_reactive_ble/test/reactive_ble_test.mocks.dart b/packages/flutter_reactive_ble/test/reactive_ble_test.mocks.dart index 56ef7901..030c1276 100644 --- a/packages/flutter_reactive_ble/test/reactive_ble_test.mocks.dart +++ b/packages/flutter_reactive_ble/test/reactive_ble_test.mocks.dart @@ -146,6 +146,14 @@ class MockReactiveBlePlatform extends _i1.Mock _i2.Result<_i2.Unit, _i2.GenericFailure<_i2.ClearGattCacheError>?>>); @override + _i4.Future readRssi(String? deviceId) => (super.noSuchMethod( + Invocation.method( + #readRssi, + [deviceId], + ), + returnValue: _i4.Future.value(0), + ) as _i4.Future); + @override _i4.Stream connectToDevice( String? id, Map<_i2.Uuid, List<_i2.Uuid>>? servicesWithCharacteristicsToDiscover, diff --git a/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/PluginController.kt b/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/PluginController.kt index 78372d70..1de34c09 100644 --- a/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/PluginController.kt +++ b/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/PluginController.kt @@ -37,7 +37,8 @@ class PluginController { "negotiateMtuSize" to this::negotiateMtuSize, "requestConnectionPriority" to this::requestConnectionPriority, "discoverServices" to this::discoverServices, - "getDiscoveredServices" to this::discoverServices + "getDiscoveredServices" to this::discoverServices, + "readRssi" to this::readRssi, ) private lateinit var bleClient: com.signify.hue.flutterreactiveble.ble.BleClient @@ -273,4 +274,18 @@ class PluginController { }) .discard() } + + private fun readRssi(call: MethodCall, result: Result) { + val args = pb.ReadRssiRequest.parseFrom(call.arguments as ByteArray) + + bleClient.readRssi(args.deviceId) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ rssi -> + val info = protoConverter.convertReadRssiResult(rssi) + result.success(info.toByteArray()) + }, { error -> + result.error("read_rssi_error", error.message, null) + }) + .discard() + } } diff --git a/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/BleClient.kt b/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/BleClient.kt index f2d1f91a..35b3a365 100644 --- a/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/BleClient.kt +++ b/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/BleClient.kt @@ -48,4 +48,5 @@ interface BleClient { fun observeBleStatus(): Observable fun requestConnectionPriority(deviceId: String, priority: ConnectionPriority): Single + fun readRssi(deviceId: String): Single } diff --git a/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/BleWrapper.kt b/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/BleWrapper.kt index 08737a09..b1e9f004 100644 --- a/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/BleWrapper.kt +++ b/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/BleWrapper.kt @@ -42,7 +42,7 @@ data class EstablishedConnection(val deviceId: String, val rxConnection: RxBleCo data class EstablishConnectionFailure(val deviceId: String, val errorMessage: String) : EstablishConnectionResult() sealed class MtuNegotiateResult -data class MtuNegotiateSuccesful(val deviceId: String, val size: Int) : MtuNegotiateResult() +data class MtuNegotiateSuccessful(val deviceId: String, val size: Int) : MtuNegotiateResult() data class MtuNegotiateFailed(val deviceId: String, val errorMessage: String) : MtuNegotiateResult() sealed class CharOperationResult diff --git a/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/DeviceConnector.kt b/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/DeviceConnector.kt index 32da5ea3..2037a9f5 100644 --- a/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/DeviceConnector.kt +++ b/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/DeviceConnector.kt @@ -184,4 +184,14 @@ internal class DeviceConnector( queue.firstOrNull() == deviceId || !queue.contains(deviceId) } .takeUntil { it.isEmpty() || it.first() == deviceId } + + /** + * Reads the current RSSI value of the device + */ + internal fun readRssi(): Single = currentConnection?.let { connection -> + when (connection) { + is EstablishedConnection -> connection.rxConnection.readRssi() + is EstablishConnectionFailure -> Single.error(Throwable(connection.errorMessage)) + } + } ?: Single.error(IllegalStateException("Connection is not established")) } diff --git a/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/ReactiveBleClient.kt b/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/ReactiveBleClient.kt index b404831e..cfd5093c 100644 --- a/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/ReactiveBleClient.kt +++ b/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/ReactiveBleClient.kt @@ -237,7 +237,7 @@ open class ReactiveBleClient(private val context: Context) : BleClient { getConnection(deviceId).flatMapSingle { connectionResult -> when (connectionResult) { is EstablishedConnection -> connectionResult.rxConnection.requestMtu(size) - .map { value -> MtuNegotiateSuccesful(deviceId, value) } + .map { value -> MtuNegotiateSuccessful(deviceId, value) } is EstablishConnectionFailure -> Single.just( @@ -353,6 +353,21 @@ open class ReactiveBleClient(private val context: Context) : BleClient { } }.first(RequestConnectionPriorityFailed(deviceId, "Unknown failure")) + override fun readRssi(deviceId: String): Single = + getConnection(deviceId).flatMapSingle { connectionResult -> + when (connectionResult) { + is EstablishedConnection -> { + connectionResult.rxConnection.readRssi() + } + is EstablishConnectionFailure -> + Single.error( + java.lang.IllegalStateException( + "Reading RSSI failed. Device is not connected" + ) + ) + } + }.firstOrError() + // enable this for extra debug output on the android stack private fun enableDebugLogging() = RxBleClient .updateLogOptions( diff --git a/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/converters/ProtobufMessageConverter.kt b/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/converters/ProtobufMessageConverter.kt index 31467806..9ccf1d3e 100644 --- a/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/converters/ProtobufMessageConverter.kt +++ b/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/converters/ProtobufMessageConverter.kt @@ -7,7 +7,7 @@ import com.polidea.rxandroidble2.RxBleDeviceServices import com.signify.hue.flutterreactiveble.ble.ConnectionUpdateSuccess import com.signify.hue.flutterreactiveble.ble.MtuNegotiateFailed import com.signify.hue.flutterreactiveble.ble.MtuNegotiateResult -import com.signify.hue.flutterreactiveble.ble.MtuNegotiateSuccesful +import com.signify.hue.flutterreactiveble.ble.MtuNegotiateSuccessful import com.signify.hue.flutterreactiveble.ble.RequestConnectionPriorityFailed import com.signify.hue.flutterreactiveble.ble.RequestConnectionPriorityResult import com.signify.hue.flutterreactiveble.ble.RequestConnectionPrioritySuccess @@ -127,7 +127,7 @@ class ProtobufMessageConverter { fun convertNegotiateMtuInfo(result: MtuNegotiateResult): pb.NegotiateMtuInfo = when (result) { - is MtuNegotiateSuccesful -> pb.NegotiateMtuInfo.newBuilder() + is MtuNegotiateSuccessful -> pb.NegotiateMtuInfo.newBuilder() .setDeviceId(result.deviceId) .setMtuSize(result.size) .build() @@ -176,6 +176,12 @@ class ProtobufMessageConverter { .build() } + fun convertReadRssiResult(rssi: Int): pb.ReadRssiResult { + return pb.ReadRssiResult.newBuilder() + .setRssi(rssi) + .build() + } + private fun fromBluetoothGattService(gattService: BluetoothGattService): pb.DiscoveredService { return pb.DiscoveredService.newBuilder() .setServiceUuid(createUuidFromParcelUuid(gattService.uuid)) diff --git a/packages/reactive_ble_mobile/android/src/test/kotlin/com/signify/hue/flutterreactiveble/ble/ReactiveBleClientTest.kt b/packages/reactive_ble_mobile/android/src/test/kotlin/com/signify/hue/flutterreactiveble/ble/ReactiveBleClientTest.kt index 4546eed2..31ace0b8 100644 --- a/packages/reactive_ble_mobile/android/src/test/kotlin/com/signify/hue/flutterreactiveble/ble/ReactiveBleClientTest.kt +++ b/packages/reactive_ble_mobile/android/src/test/kotlin/com/signify/hue/flutterreactiveble/ble/ReactiveBleClientTest.kt @@ -135,8 +135,9 @@ class ReactiveBleClientTest { fun `should not call readcharacteristic in case the connection is not established`() { subject.onNext(EstablishConnectionFailure("test", "error")) - sut.readCharacteristic("test", UUID.randomUUID(), 11).test() + val result = sut.readCharacteristic("test", UUID.randomUUID(), 11).test() + assertThat(result.values().first()).isInstanceOf(CharOperationFailed::class.java) verify(exactly = 0) { rxConnection.readCharacteristic(any()) } } @@ -192,7 +193,8 @@ class ReactiveBleClientTest { val bytes = byteArrayOf(byteMin, byteMax) subject.onNext(EstablishConnectionFailure("test", "error")) - sut.writeCharacteristicWithResponse("test", UUID.randomUUID(), 11, bytes).test() + val result = sut.writeCharacteristicWithResponse("test", UUID.randomUUID(), 11, bytes).test() + assertThat(result.values().first()).isInstanceOf(CharOperationFailed::class.java) verify(exactly = 0) { rxConnection.writeCharWithResponse(any(), any()) } } @@ -216,9 +218,9 @@ class ReactiveBleClientTest { val bytes = byteArrayOf(byteMin, byteMax) subject.onNext(EstablishConnectionFailure("test", "error")) + val result = sut.writeCharacteristicWithoutResponse("test", UUID.randomUUID(), 11, bytes).test() - sut.writeCharacteristicWithoutResponse("test", UUID.randomUUID(), 11, bytes).test() - + assertThat(result.values().first()).isInstanceOf(CharOperationFailed::class.java) verify(exactly = 0) { rxConnection.writeCharWithoutResponse(any(), any()) } } @@ -261,7 +263,7 @@ class ReactiveBleClientTest { val result = sut.negotiateMtuSize("", mtuSize).test() - assertThat(result.values().first()).isInstanceOf(MtuNegotiateSuccesful::class.java) + assertThat(result.values().first()).isInstanceOf(MtuNegotiateSuccessful::class.java) } @Test @@ -275,6 +277,30 @@ class ReactiveBleClientTest { } } + @Nested + @DisplayName("Read RSSI") + inner class ReadRssiTest { + + @Test + fun `should return RSSI in case it succeeds`() { + val rssi = -42 + every { rxConnection.readRssi() }.returns(Single.just(rssi)) + + val result = sut.readRssi("").test() + + assertThat(result.values().first()).isEqualTo(rssi) + } + + @Test + fun `should return error in case it fails`() { + every { rxConnection.readRssi() }.returns(Single.error(IllegalStateException("boom"))) + + val result = sut.readRssi("").test() + + assertThat(result.errors().first()).isInstanceOf(IllegalStateException::class.java) + } + } + @Nested @DisplayName("Observe status") inner class ObserveBleStatusTest { @@ -309,7 +335,7 @@ class ReactiveBleClientTest { inner class ChangePriorityTest { @Test - fun `returns prioritysuccess when completed`() { + fun `returns prioritysuccess when completed`() { val completer = Completable.fromCallable { true } every { rxConnection.requestConnectionPriority(any(), any(), any()) }.returns(completer) diff --git a/packages/reactive_ble_mobile/android/src/test/kotlin/com/signify/hue/flutterreactiveble/converters/ProtobufMessageConverterTest.kt b/packages/reactive_ble_mobile/android/src/test/kotlin/com/signify/hue/flutterreactiveble/converters/ProtobufMessageConverterTest.kt index 6b5f3246..c111661f 100644 --- a/packages/reactive_ble_mobile/android/src/test/kotlin/com/signify/hue/flutterreactiveble/converters/ProtobufMessageConverterTest.kt +++ b/packages/reactive_ble_mobile/android/src/test/kotlin/com/signify/hue/flutterreactiveble/converters/ProtobufMessageConverterTest.kt @@ -5,7 +5,7 @@ import com.google.protobuf.ByteString import com.signify.hue.flutterreactiveble.ble.Connectable import com.signify.hue.flutterreactiveble.ble.ConnectionUpdateSuccess import com.signify.hue.flutterreactiveble.ble.MtuNegotiateFailed -import com.signify.hue.flutterreactiveble.ble.MtuNegotiateSuccesful +import com.signify.hue.flutterreactiveble.ble.MtuNegotiateSuccessful import com.signify.hue.flutterreactiveble.ble.ScanInfo import com.signify.hue.flutterreactiveble.model.NegotiateMtuErrorType import org.junit.jupiter.api.DisplayName @@ -137,7 +137,7 @@ class ProtobufMessageConverterTest { @Test fun `converts a char value and request into a characteristic info value `() { - val request = createCharacteristicRequest("a", UUID.randomUUID()) + val request = createCharacteristicRequest("b", UUID.randomUUID()) val expectedValue = byteArrayOf(1) val valueInfo = protobufConverter.convertCharacteristicInfo(request.characteristic, expectedValue) @@ -151,26 +151,26 @@ class ProtobufMessageConverterTest { @Test fun `converts to negotiatemtuinfo object`() { - val result = MtuNegotiateSuccesful("", 3) + val result = MtuNegotiateSuccessful("", 3) assertThat(protobufConverter.convertNegotiateMtuInfo(result)).isInstanceOf(pb.NegotiateMtuInfo::class.java) } @Test fun `converts deviceId`() { - val result = MtuNegotiateSuccesful("id", 3) + val result = MtuNegotiateSuccessful("id", 3) assertThat(protobufConverter.convertNegotiateMtuInfo(result).deviceId).isEqualTo(result.deviceId) } @Test fun `converts mtusize`() { - val result = MtuNegotiateSuccesful("id", 3) + val result = MtuNegotiateSuccessful("id", 3) assertThat(protobufConverter.convertNegotiateMtuInfo(result).mtuSize).isEqualTo(result.size) } @Test fun `sets default value for error in case no error occurred`() { - val result = MtuNegotiateSuccesful("id", 3) + val result = MtuNegotiateSuccessful("id", 3) assertThat(protobufConverter.convertNegotiateMtuInfo(result).failure.message).isEqualTo("") } @@ -191,7 +191,7 @@ class ProtobufMessageConverterTest { } private fun createScanInfo(): ScanInfo { - val macAdress = "123" + val macAddress = "123" val deviceName = "Testdevice" val rssi = 200 val uuid = UUID.randomUUID() @@ -204,7 +204,7 @@ class ProtobufMessageConverterTest { val manufacturerData = "123".toByteArray() return ScanInfo( - deviceId = macAdress, + deviceId = macAddress, name = deviceName, rssi = rssi, connectable = Connectable.UNKNOWN, diff --git a/packages/reactive_ble_mobile/ios/Classes/BleData/bledata.pb.swift b/packages/reactive_ble_mobile/ios/Classes/BleData/bledata.pb.swift index dda4f02f..bbc4e9b3 100644 --- a/packages/reactive_ble_mobile/ios/Classes/BleData/bledata.pb.swift +++ b/packages/reactive_ble_mobile/ios/Classes/BleData/bledata.pb.swift @@ -605,6 +605,30 @@ struct DiscoveredCharacteristic { fileprivate var _serviceID: Uuid? = nil } +struct ReadRssiRequest { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var deviceID: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct ReadRssiResult { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var rssi: Int32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + struct Uuid { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -670,6 +694,8 @@ extension DiscoverServicesRequest: @unchecked Sendable {} extension DiscoverServicesInfo: @unchecked Sendable {} extension DiscoveredService: @unchecked Sendable {} extension DiscoveredCharacteristic: @unchecked Sendable {} +extension ReadRssiRequest: @unchecked Sendable {} +extension ReadRssiResult: @unchecked Sendable {} extension Uuid: @unchecked Sendable {} extension GenericFailure: @unchecked Sendable {} extension IsConnectable: @unchecked Sendable {} @@ -1817,6 +1843,70 @@ extension DiscoveredCharacteristic: SwiftProtobuf.Message, SwiftProtobuf._Messag } } +extension ReadRssiRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "ReadRssiRequest" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "deviceId"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.deviceID) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.deviceID.isEmpty { + try visitor.visitSingularStringField(value: self.deviceID, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: ReadRssiRequest, rhs: ReadRssiRequest) -> Bool { + if lhs.deviceID != rhs.deviceID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension ReadRssiResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "ReadRssiResult" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "rssi"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt32Field(value: &self.rssi) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.rssi != 0 { + try visitor.visitSingularInt32Field(value: self.rssi, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: ReadRssiResult, rhs: ReadRssiResult) -> Bool { + if lhs.rssi != rhs.rssi {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Uuid: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = "Uuid" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ diff --git a/packages/reactive_ble_mobile/ios/Classes/Plugin/PluginController.swift b/packages/reactive_ble_mobile/ios/Classes/Plugin/PluginController.swift index ae6637aa..6190cacc 100644 --- a/packages/reactive_ble_mobile/ios/Classes/Plugin/PluginController.swift +++ b/packages/reactive_ble_mobile/ios/Classes/Plugin/PluginController.swift @@ -580,6 +580,50 @@ final class PluginController { completion(.success(result)) } + + func readRssi(name: String, args: ReadRssiRequest, completion: @escaping PlatformMethodCompletionHandler) { + guard let central = central + else { + completion(.failure(PluginError.notInitialized.asFlutterError)) + return + } + + guard let peripheralID = UUID(uuidString: args.deviceID) + else { + completion(.failure(PluginError.invalidMethodCall(method: name, details: "peripheral ID is required").asFlutterError)) + return + } + + do { + try central.readRssi( + for: peripheralID, + completion: papply(weak: self) { context, result in + result.iif( + success: { rssi in + let result: ReadRssiResult = ReadRssiResult.with { + $0.rssi = Int32(rssi) + } + completion(.success(result)) + }, + failure: { error in + completion(.failure(context.makeFlutterError(error: error))) + } + ) + } + ) + } catch let error { + completion(.failure(makeFlutterError(error: error))) + } + } + + // takes an error and converts it into a Flutter error + private func makeFlutterError(error: Error) -> FlutterError { + if let error = error as? PluginError { + return error.asFlutterError + } else { + return PluginError.unknown(error).asFlutterError + } + } private func reportState(_ knownState: CBManagerState? = nil) { guard let sink = stateSink diff --git a/packages/reactive_ble_mobile/ios/Classes/Plugin/SwiftReactiveBlePlugin.swift b/packages/reactive_ble_mobile/ios/Classes/Plugin/SwiftReactiveBlePlugin.swift index 43156511..15bf162f 100644 --- a/packages/reactive_ble_mobile/ios/Classes/Plugin/SwiftReactiveBlePlugin.swift +++ b/packages/reactive_ble_mobile/ios/Classes/Plugin/SwiftReactiveBlePlugin.swift @@ -140,6 +140,9 @@ public class SwiftReactiveBlePlugin: NSObject, FlutterPlugin { }), AnyPlatformMethod(UnaryPlatformMethod(name: "negotiateMtuSize") { (name, context, args: NegotiateMtuRequest, completion) in context.reportMaximumWriteValueLength(name: name, args: args, completion: completion) + }), + AnyPlatformMethod(UnaryPlatformMethod(name: "readRssi") { (name, context, args: ReadRssiRequest, completion) in + context.readRssi(name: name, args: args, completion: completion) }) ]) diff --git a/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/Central.swift b/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/Central.swift index 3ca068a2..60e5f34a 100644 --- a/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/Central.swift +++ b/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/Central.swift @@ -33,6 +33,7 @@ final class Central { private let servicesWithCharacteristicsDiscoveryRegistry = PeripheralTaskRegistry() private let characteristicNotifyRegistry = PeripheralTaskRegistry() private let characteristicWriteRegistry = PeripheralTaskRegistry() + private let readRssiRegistry = PeripheralTaskRegistry() init( onStateChange: @escaping StateChangeHandler, @@ -112,6 +113,12 @@ final class Central { key: q, action: { $0.handleWrite(error: error) } ) + }, + onReadRssi: papply(weak: self) { central, peripheral, rssi, error in + central.readRssiRegistry.updateTask( + key: peripheral.identifier, + action: { $0.handleReadRssi(rssi: rssi, error: error) } + ) } ) self.centralManager = CBCentralManager( @@ -303,6 +310,24 @@ final class Central { return peripheral.maximumWriteValueLength(for: type) } + + func readRssi(for peripheralId: PeripheralID, completion: @escaping (Failable) -> Void) throws { + let peripheral = try resolve(connected: peripheralId) + + readRssiRegistry.registerTask( + key: peripheralId, + params: .init(), + completion: completion + ) + + readRssiRegistry.updateTask( + key: peripheralId, + action: { + $0.start(peripheral: peripheral) + } + ) + } + private func eject(_ peripheral: CBPeripheral, error: Error) { peripheral.delegate = nil activePeripherals[peripheral.identifier] = nil diff --git a/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/PeripheralDelegate.swift b/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/PeripheralDelegate.swift index 0c50851b..3b30f57f 100644 --- a/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/PeripheralDelegate.swift +++ b/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/PeripheralDelegate.swift @@ -7,25 +7,29 @@ final class PeripheralDelegate: NSObject, CBPeripheralDelegate { typealias CharacteristicNotificationStateUpdateHandler = (CBCharacteristic, Error?) -> Void typealias CharacteristicValueUpdateHandler = (CBCharacteristic, Error?) -> Void typealias CharacteristicValueWriteHandler = (CBCharacteristic, Error?) -> Void + typealias ReadRssiHandler = (CBPeripheral, Int, Error?) -> Void private let onServicesDiscovery: ServicesDiscoveryHandler private let onCharacteristicsDiscovery: CharacteristicsDiscoverHandler private let onCharacteristicNotificationStateUpdate: CharacteristicNotificationStateUpdateHandler private let onCharacteristicValueUpdate: CharacteristicValueUpdateHandler private let onCharacteristicValueWrite: CharacteristicValueWriteHandler + private let onReadRssi: ReadRssiHandler init( onServicesDiscovery: @escaping ServicesDiscoveryHandler, onCharacteristicsDiscovery: @escaping CharacteristicsDiscoverHandler, onCharacteristicNotificationStateUpdate: @escaping CharacteristicNotificationStateUpdateHandler, onCharacteristicValueUpdate: @escaping CharacteristicValueUpdateHandler, - onCharacteristicValueWrite: @escaping CharacteristicValueWriteHandler + onCharacteristicValueWrite: @escaping CharacteristicValueWriteHandler, + onReadRssi: @escaping ReadRssiHandler ) { self.onServicesDiscovery = onServicesDiscovery self.onCharacteristicsDiscovery = onCharacteristicsDiscovery self.onCharacteristicNotificationStateUpdate = onCharacteristicNotificationStateUpdate self.onCharacteristicValueUpdate = onCharacteristicValueUpdate self.onCharacteristicValueWrite = onCharacteristicValueWrite + self.onReadRssi = onReadRssi } func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { @@ -47,4 +51,8 @@ final class PeripheralDelegate: NSObject, CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { onCharacteristicValueWrite(characteristic, error) } + + func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) { + onReadRssi(peripheral, RSSI.intValue, error) + } } diff --git a/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/Tasks/ReadRssi/ReadRssiTaskController.swift b/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/Tasks/ReadRssi/ReadRssiTaskController.swift new file mode 100644 index 00000000..efb7a0fa --- /dev/null +++ b/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/Tasks/ReadRssi/ReadRssiTaskController.swift @@ -0,0 +1,42 @@ +import CoreBluetooth + +struct ReadRssiTaskController: PeripheralTaskController { + typealias TaskSpec = ReadRssiTaskSpec + + private let task: SubjectTask + + init(_ task: SubjectTask) { + self.task = task + } + + func start(peripheral: CBPeripheral) -> SubjectTask { + // error if not connected + guard peripheral.state == .connected + else { + return task.with( + state: task.state.finished( + .failure(PluginError.internalInconcictency(details: nil)) + ) + ) + } + + peripheral.readRSSI() + + return task.with(state: task.state.processing(.readingRssi)) + } + + func cancel(error: Error) -> SubjectTask { + return task + .with(state: task.state.finished(.failure(error))) + } + + func handleReadRssi(rssi: Int, error: Error?) -> SubjectTask { + if let error = error { + return task + .with(state: task.state.finished(.failure(error))) + } else { + return task + .with(state: task.state.finished(.success(rssi))) + } + } +} diff --git a/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/Tasks/ReadRssi/ReadRssiTaskSpec.swift b/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/Tasks/ReadRssi/ReadRssiTaskSpec.swift new file mode 100644 index 00000000..54d545cf --- /dev/null +++ b/packages/reactive_ble_mobile/ios/Classes/ReactiveBle/Tasks/ReadRssi/ReadRssiTaskSpec.swift @@ -0,0 +1,17 @@ +struct ReadRssiTaskSpec: PeripheralTaskSpec { + typealias Key = PeripheralID + + struct Params {} + + enum Stage { + case readingRssi + } + + typealias Result = Failable + + static let tag = "READ RSSI" + + static func isMember(_ key: Key, of group: PeripheralID) -> Bool { + return key == group + } +} diff --git a/packages/reactive_ble_mobile/lib/src/converter/args_to_protubuf_converter.dart b/packages/reactive_ble_mobile/lib/src/converter/args_to_protubuf_converter.dart index b88c2145..875c45ec 100644 --- a/packages/reactive_ble_mobile/lib/src/converter/args_to_protubuf_converter.dart +++ b/packages/reactive_ble_mobile/lib/src/converter/args_to_protubuf_converter.dart @@ -47,6 +47,8 @@ abstract class ArgsToProtobufConverter { pb.ClearGattCacheRequest createClearGattCacheRequest(String deviceId); pb.DiscoverServicesRequest createDiscoverServicesRequest(String deviceId); + + pb.ReadRssiRequest createReadRssiRequest(String deviceId); } class ArgsToProtobufConverterImpl implements ArgsToProtobufConverter { @@ -206,4 +208,10 @@ class ArgsToProtobufConverterImpl implements ArgsToProtobufConverter { final args = pb.DiscoverServicesRequest()..deviceId = deviceId; return args; } + + @override + pb.ReadRssiRequest createReadRssiRequest(String deviceId) { + final args = pb.ReadRssiRequest()..deviceId = deviceId; + return args; + } } diff --git a/packages/reactive_ble_mobile/lib/src/converter/protobuf_converter.dart b/packages/reactive_ble_mobile/lib/src/converter/protobuf_converter.dart index c4c25d48..cd223eb2 100644 --- a/packages/reactive_ble_mobile/lib/src/converter/protobuf_converter.dart +++ b/packages/reactive_ble_mobile/lib/src/converter/protobuf_converter.dart @@ -25,6 +25,8 @@ abstract class ProtobufConverter { pb.NegotiateMtuInfo.fromBuffer(data).mtuSize; List discoveredServicesFrom(List data); + + int readRssiResultFrom(List data); } class ProtobufConverterImpl implements ProtobufConverter { @@ -198,6 +200,10 @@ class ProtobufConverterImpl implements ProtobufConverter { return message.services.map(_convertService).toList(growable: false); } + @override + int readRssiResultFrom(List data) => + pb.ReadRssiResult.fromBuffer(data).rssi; + DiscoveredService _convertService(pb.DiscoveredService service) => DiscoveredService( serviceId: Uuid(service.serviceUuid.data), diff --git a/packages/reactive_ble_mobile/lib/src/generated/bledata.pb.dart b/packages/reactive_ble_mobile/lib/src/generated/bledata.pb.dart index 92f29cb0..e2d1c4b6 100644 --- a/packages/reactive_ble_mobile/lib/src/generated/bledata.pb.dart +++ b/packages/reactive_ble_mobile/lib/src/generated/bledata.pb.dart @@ -1781,6 +1781,100 @@ class DiscoveredCharacteristic extends $pb.GeneratedMessage { void clearCharacteristicInstanceId() => clearField(8); } +class ReadRssiRequest extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ReadRssiRequest', createEmptyInstance: create) + ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deviceId', protoName: 'deviceId') + ..hasRequiredFields = false + ; + + ReadRssiRequest._() : super(); + factory ReadRssiRequest({ + $core.String? deviceId, + }) { + final _result = create(); + if (deviceId != null) { + _result.deviceId = deviceId; + } + return _result; + } + factory ReadRssiRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory ReadRssiRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + ReadRssiRequest clone() => ReadRssiRequest()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + ReadRssiRequest copyWith(void Function(ReadRssiRequest) updates) => super.copyWith((message) => updates(message as ReadRssiRequest)) as ReadRssiRequest; // ignore: deprecated_member_use + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static ReadRssiRequest create() => ReadRssiRequest._(); + ReadRssiRequest createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ReadRssiRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static ReadRssiRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get deviceId => $_getSZ(0); + @$pb.TagNumber(1) + set deviceId($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasDeviceId() => $_has(0); + @$pb.TagNumber(1) + void clearDeviceId() => clearField(1); +} + +class ReadRssiResult extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ReadRssiResult', createEmptyInstance: create) + ..a<$core.int>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rssi', $pb.PbFieldType.O3) + ..hasRequiredFields = false + ; + + ReadRssiResult._() : super(); + factory ReadRssiResult({ + $core.int? rssi, + }) { + final _result = create(); + if (rssi != null) { + _result.rssi = rssi; + } + return _result; + } + factory ReadRssiResult.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory ReadRssiResult.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + ReadRssiResult clone() => ReadRssiResult()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + ReadRssiResult copyWith(void Function(ReadRssiResult) updates) => super.copyWith((message) => updates(message as ReadRssiResult)) as ReadRssiResult; // ignore: deprecated_member_use + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static ReadRssiResult create() => ReadRssiResult._(); + ReadRssiResult createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ReadRssiResult getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static ReadRssiResult? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get rssi => $_getIZ(0); + @$pb.TagNumber(1) + set rssi($core.int v) { $_setSignedInt32(0, v); } + @$pb.TagNumber(1) + $core.bool hasRssi() => $_has(0); + @$pb.TagNumber(1) + void clearRssi() => clearField(1); +} + class Uuid extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Uuid', createEmptyInstance: create) ..a<$core.List<$core.int>>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY) diff --git a/packages/reactive_ble_mobile/lib/src/generated/bledata.pbjson.dart b/packages/reactive_ble_mobile/lib/src/generated/bledata.pbjson.dart index fbee668d..aaa1515f 100644 --- a/packages/reactive_ble_mobile/lib/src/generated/bledata.pbjson.dart +++ b/packages/reactive_ble_mobile/lib/src/generated/bledata.pbjson.dart @@ -308,6 +308,26 @@ const DiscoveredCharacteristic$json = const { /// Descriptor for `DiscoveredCharacteristic`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List discoveredCharacteristicDescriptor = $convert.base64Decode('ChhEaXNjb3ZlcmVkQ2hhcmFjdGVyaXN0aWMSMQoQY2hhcmFjdGVyaXN0aWNJZBgBIAEoCzIFLlV1aWRSEGNoYXJhY3RlcmlzdGljSWQSIwoJc2VydmljZUlkGAIgASgLMgUuVXVpZFIJc2VydmljZUlkEh4KCmlzUmVhZGFibGUYAyABKAhSCmlzUmVhZGFibGUSNgoWaXNXcml0YWJsZVdpdGhSZXNwb25zZRgEIAEoCFIWaXNXcml0YWJsZVdpdGhSZXNwb25zZRI8Chlpc1dyaXRhYmxlV2l0aG91dFJlc3BvbnNlGAUgASgIUhlpc1dyaXRhYmxlV2l0aG91dFJlc3BvbnNlEiIKDGlzTm90aWZpYWJsZRgGIAEoCFIMaXNOb3RpZmlhYmxlEiQKDWlzSW5kaWNhdGFibGUYByABKAhSDWlzSW5kaWNhdGFibGUSOgoYY2hhcmFjdGVyaXN0aWNJbnN0YW5jZUlkGAggASgJUhhjaGFyYWN0ZXJpc3RpY0luc3RhbmNlSWQ='); +@$core.Deprecated('Use readRssiRequestDescriptor instead') +const ReadRssiRequest$json = const { + '1': 'ReadRssiRequest', + '2': const [ + const {'1': 'deviceId', '3': 1, '4': 1, '5': 9, '10': 'deviceId'}, + ], +}; + +/// Descriptor for `ReadRssiRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List readRssiRequestDescriptor = $convert.base64Decode('Cg9SZWFkUnNzaVJlcXVlc3QSGgoIZGV2aWNlSWQYASABKAlSCGRldmljZUlk'); +@$core.Deprecated('Use readRssiResultDescriptor instead') +const ReadRssiResult$json = const { + '1': 'ReadRssiResult', + '2': const [ + const {'1': 'rssi', '3': 1, '4': 1, '5': 5, '10': 'rssi'}, + ], +}; + +/// Descriptor for `ReadRssiResult`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List readRssiResultDescriptor = $convert.base64Decode('Cg5SZWFkUnNzaVJlc3VsdBISCgRyc3NpGAEgASgFUgRyc3Np'); @$core.Deprecated('Use uuidDescriptor instead') const Uuid$json = const { '1': 'Uuid', diff --git a/packages/reactive_ble_mobile/lib/src/reactive_ble_mobile_platform.dart b/packages/reactive_ble_mobile/lib/src/reactive_ble_mobile_platform.dart index 1e41e7bc..2c36a366 100644 --- a/packages/reactive_ble_mobile/lib/src/reactive_ble_mobile_platform.dart +++ b/packages/reactive_ble_mobile/lib/src/reactive_ble_mobile_platform.dart @@ -302,6 +302,16 @@ class ReactiveBleMobilePlatform extends ReactiveBlePlatform { ) .then((data) => _protobufConverter.discoveredServicesFrom(data!)); } + + @override + Future readRssi(String deviceId) async => _bleMethodChannel + .invokeMethod>( + "readRssi", + _argsToProtobufConverter + .createReadRssiRequest(deviceId) + .writeToBuffer(), + ) + .then((data) => _protobufConverter.readRssiResultFrom(data!)); } class ReactiveBleMobilePlatformFactory { diff --git a/packages/reactive_ble_mobile/protos/bledata.proto b/packages/reactive_ble_mobile/protos/bledata.proto index 33e7684e..4e80f442 100644 --- a/packages/reactive_ble_mobile/protos/bledata.proto +++ b/packages/reactive_ble_mobile/protos/bledata.proto @@ -146,6 +146,14 @@ message DiscoveredCharacteristic { string characteristicInstanceId = 8; } +message ReadRssiRequest { + string deviceId = 1; +} + +message ReadRssiResult { + int32 rssi = 1; +} + message Uuid { bytes data = 1; } diff --git a/packages/reactive_ble_mobile/test/reactive_ble_platform_test.mocks.dart b/packages/reactive_ble_mobile/test/reactive_ble_platform_test.mocks.dart index 75eda560..caea5db2 100644 --- a/packages/reactive_ble_mobile/test/reactive_ble_platform_test.mocks.dart +++ b/packages/reactive_ble_mobile/test/reactive_ble_platform_test.mocks.dart @@ -149,8 +149,9 @@ class _FakeDiscoverServicesRequest_10 extends _i1.SmartFake ); } -class _FakeScanResult_11 extends _i1.SmartFake implements _i3.ScanResult { - _FakeScanResult_11( +class _FakeReadRssiRequest_11 extends _i1.SmartFake + implements _i2.ReadRssiRequest { + _FakeReadRssiRequest_11( Object parent, Invocation parentInvocation, ) : super( @@ -159,9 +160,19 @@ class _FakeScanResult_11 extends _i1.SmartFake implements _i3.ScanResult { ); } -class _FakeConnectionStateUpdate_12 extends _i1.SmartFake +class _FakeScanResult_12 extends _i1.SmartFake implements _i3.ScanResult { + _FakeScanResult_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeConnectionStateUpdate_13 extends _i1.SmartFake implements _i3.ConnectionStateUpdate { - _FakeConnectionStateUpdate_12( + _FakeConnectionStateUpdate_13( Object parent, Invocation parentInvocation, ) : super( @@ -170,9 +181,9 @@ class _FakeConnectionStateUpdate_12 extends _i1.SmartFake ); } -class _FakeResult_13 extends _i1.SmartFake +class _FakeResult_14 extends _i1.SmartFake implements _i3.Result { - _FakeResult_13( + _FakeResult_14( Object parent, Invocation parentInvocation, ) : super( @@ -181,9 +192,9 @@ class _FakeResult_13 extends _i1.SmartFake ); } -class _FakeCharacteristicValue_14 extends _i1.SmartFake +class _FakeCharacteristicValue_15 extends _i1.SmartFake implements _i3.CharacteristicValue { - _FakeCharacteristicValue_14( + _FakeCharacteristicValue_15( Object parent, Invocation parentInvocation, ) : super( @@ -192,9 +203,9 @@ class _FakeCharacteristicValue_14 extends _i1.SmartFake ); } -class _FakeWriteCharacteristicInfo_15 extends _i1.SmartFake +class _FakeWriteCharacteristicInfo_16 extends _i1.SmartFake implements _i3.WriteCharacteristicInfo { - _FakeWriteCharacteristicInfo_15( + _FakeWriteCharacteristicInfo_16( Object parent, Invocation parentInvocation, ) : super( @@ -203,9 +214,9 @@ class _FakeWriteCharacteristicInfo_15 extends _i1.SmartFake ); } -class _FakeConnectionPriorityInfo_16 extends _i1.SmartFake +class _FakeConnectionPriorityInfo_17 extends _i1.SmartFake implements _i3.ConnectionPriorityInfo { - _FakeConnectionPriorityInfo_16( + _FakeConnectionPriorityInfo_17( Object parent, Invocation parentInvocation, ) : super( @@ -214,8 +225,8 @@ class _FakeConnectionPriorityInfo_16 extends _i1.SmartFake ); } -class _FakeMethodCodec_17 extends _i1.SmartFake implements _i4.MethodCodec { - _FakeMethodCodec_17( +class _FakeMethodCodec_18 extends _i1.SmartFake implements _i4.MethodCodec { + _FakeMethodCodec_18( Object parent, Invocation parentInvocation, ) : super( @@ -224,9 +235,9 @@ class _FakeMethodCodec_17 extends _i1.SmartFake implements _i4.MethodCodec { ); } -class _FakeBinaryMessenger_18 extends _i1.SmartFake +class _FakeBinaryMessenger_19 extends _i1.SmartFake implements _i5.BinaryMessenger { - _FakeBinaryMessenger_18( + _FakeBinaryMessenger_19( Object parent, Invocation parentInvocation, ) : super( @@ -466,6 +477,21 @@ class MockArgsToProtobufConverter extends _i1.Mock ), ), ) as _i2.DiscoverServicesRequest); + @override + _i2.ReadRssiRequest createReadRssiRequest(String? deviceId) => + (super.noSuchMethod( + Invocation.method( + #createReadRssiRequest, + [deviceId], + ), + returnValue: _FakeReadRssiRequest_11( + this, + Invocation.method( + #createReadRssiRequest, + [deviceId], + ), + ), + ) as _i2.ReadRssiRequest); } /// A class which mocks [ProtobufConverter]. @@ -490,7 +516,7 @@ class MockProtobufConverter extends _i1.Mock implements _i7.ProtobufConverter { #scanResultFrom, [data], ), - returnValue: _FakeScanResult_11( + returnValue: _FakeScanResult_12( this, Invocation.method( #scanResultFrom, @@ -505,7 +531,7 @@ class MockProtobufConverter extends _i1.Mock implements _i7.ProtobufConverter { #connectionStateUpdateFrom, [data], ), - returnValue: _FakeConnectionStateUpdate_12( + returnValue: _FakeConnectionStateUpdate_13( this, Invocation.method( #connectionStateUpdateFrom, @@ -520,7 +546,7 @@ class MockProtobufConverter extends _i1.Mock implements _i7.ProtobufConverter { #clearGattCacheResultFrom, [data], ), - returnValue: _FakeResult_13<_i3.Unit, + returnValue: _FakeResult_14<_i3.Unit, _i3.GenericFailure<_i3.ClearGattCacheError>?>( this, Invocation.method( @@ -537,7 +563,7 @@ class MockProtobufConverter extends _i1.Mock implements _i7.ProtobufConverter { #characteristicValueFrom, [data], ), - returnValue: _FakeCharacteristicValue_14( + returnValue: _FakeCharacteristicValue_15( this, Invocation.method( #characteristicValueFrom, @@ -552,7 +578,7 @@ class MockProtobufConverter extends _i1.Mock implements _i7.ProtobufConverter { #writeCharacteristicInfoFrom, [data], ), - returnValue: _FakeWriteCharacteristicInfo_15( + returnValue: _FakeWriteCharacteristicInfo_16( this, Invocation.method( #writeCharacteristicInfoFrom, @@ -567,7 +593,7 @@ class MockProtobufConverter extends _i1.Mock implements _i7.ProtobufConverter { #connectionPriorityInfoFrom, [data], ), - returnValue: _FakeConnectionPriorityInfo_16( + returnValue: _FakeConnectionPriorityInfo_17( this, Invocation.method( #connectionPriorityInfoFrom, @@ -592,6 +618,14 @@ class MockProtobufConverter extends _i1.Mock implements _i7.ProtobufConverter { ), returnValue: <_i3.DiscoveredService>[], ) as List<_i3.DiscoveredService>); + @override + int readRssiResultFrom(List? data) => (super.noSuchMethod( + Invocation.method( + #readRssiResultFrom, + [data], + ), + returnValue: 0, + ) as int); } /// A class which mocks [MethodChannel]. @@ -610,7 +644,7 @@ class MockMethodChannel extends _i1.Mock implements _i8.MethodChannel { @override _i4.MethodCodec get codec => (super.noSuchMethod( Invocation.getter(#codec), - returnValue: _FakeMethodCodec_17( + returnValue: _FakeMethodCodec_18( this, Invocation.getter(#codec), ), @@ -618,7 +652,7 @@ class MockMethodChannel extends _i1.Mock implements _i8.MethodChannel { @override _i5.BinaryMessenger get binaryMessenger => (super.noSuchMethod( Invocation.getter(#binaryMessenger), - returnValue: _FakeBinaryMessenger_18( + returnValue: _FakeBinaryMessenger_19( this, Invocation.getter(#binaryMessenger), ), diff --git a/packages/reactive_ble_platform_interface/lib/src/reactive_ble_platform_interface.dart b/packages/reactive_ble_platform_interface/lib/src/reactive_ble_platform_interface.dart index 61b88007..6c620641 100644 --- a/packages/reactive_ble_platform_interface/lib/src/reactive_ble_platform_interface.dart +++ b/packages/reactive_ble_platform_interface/lib/src/reactive_ble_platform_interface.dart @@ -87,6 +87,11 @@ abstract class ReactiveBlePlatform extends PlatformInterface { throw UnimplementedError('clearGattCache() has not been implemented.'); } + Future readRssi(String deviceId) async { + throw UnimplementedError( + 'readRssi(String deviceId) has not been implemented.'); + } + /// Connects to a specific device and the connection remains `established` until /// the stream is `cancelled` or the connection is closed by the peripheral. ///