Skip to content

Commit

Permalink
Read RSSI (#796)
Browse files Browse the repository at this point in the history
* Read RSSI of connected device

* Kotlin style issues

* Address review comments and add tests

---------

Co-authored-by: Tyler Norbury <[email protected]>
Co-authored-by: Sander Kersten <[email protected]>
  • Loading branch information
3 people authored Jan 25, 2024
1 parent b397004 commit a3e4148
Show file tree
Hide file tree
Showing 33 changed files with 627 additions and 49 deletions.
1 change: 1 addition & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ void main() {
return _ble.getDiscoveredServices(deviceId);
},
logMessage: _bleLogger.addToLog,
readRssi: _ble.readRssi,
);
runApp(
MultiProvider(
Expand Down
3 changes: 3 additions & 0 deletions example/lib/src/ble/ble_device_interactor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ class BleDeviceInteractor {
BleDeviceInteractor({
required Future<List<Service>> Function(String deviceId) bleDiscoverServices,
required void Function(String message) logMessage,
required this.readRssi,
}) : _bleDiscoverServices = bleDiscoverServices,
_logMessage = logMessage;

final Future<List<Service>> Function(String deviceId) _bleDiscoverServices;

final Future<int> Function(String deviceId) readRssi;

final void Function(String message) _logMessage;

Future<List<Service>> discoverServices(String deviceId) async {
Expand Down
29 changes: 27 additions & 2 deletions example/lib/src/ui/device_detail/device_interaction_tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class DeviceInteractionTab extends StatelessWidget {
connectionStatus: connectionStateUpdate.connectionState,
deviceConnector: deviceConnector,
discoverServices: () => serviceDiscoverer.discoverServices(device.id),
readRssi: () => serviceDiscoverer.readRssi(device.id),
),
),
);
Expand All @@ -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<int> Function() readRssi;

@CustomEquality(Ignore())
final Future<List<Service>> Function() discoverServices;
Expand Down Expand Up @@ -78,6 +81,8 @@ class _DeviceInteractionTab extends StatefulWidget {
class _DeviceInteractionTabState extends State<_DeviceInteractionTab> {
late List<Service> discoveredServices;

int _rssi = 0;

@override
void initState() {
discoveredServices = [];
Expand All @@ -91,6 +96,13 @@ class _DeviceInteractionTabState extends State<_DeviceInteractionTab> {
});
}

Future<void> readRssi() async {
final rssi = await widget.viewModel.readRssi();
setState(() {
_rssi = rssi;
});
}

@override
Widget build(BuildContext context) => CustomScrollView(
slivers: [
Expand Down Expand Up @@ -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: <Widget>[
ElevatedButton(
onPressed: !widget.viewModel.deviceConnected ? widget.viewModel.connect : null,
Expand All @@ -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"),
),
],
),
),
Expand Down
22 changes: 19 additions & 3 deletions example/lib/src/ui/device_detail/device_interaction_tab.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions packages/flutter_reactive_ble/lib/src/reactive_ble.dart
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,12 @@ class FlutterReactiveBle {
Future<void> 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<int> readRssi(String deviceId) async =>
_blePlatform.readRssi(deviceId);

/// Subscribes to updates from the characteristic specified.
///
/// This stream terminates automatically when the device is disconnected.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ class MockReactiveBlePlatform extends _i1.Mock
_i2.Result<_i2.Unit,
_i2.GenericFailure<_i2.ClearGattCacheError>?>>);
@override
_i4.Future<int> readRssi(String? deviceId) => (super.noSuchMethod(
Invocation.method(
#readRssi,
[deviceId],
),
returnValue: _i4.Future<int>.value(0),
) as _i4.Future<int>);
@override
_i4.Stream<void> connectToDevice(
String? id,
Map<_i2.Uuid, List<_i2.Uuid>>? servicesWithCharacteristicsToDiscover,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ class MockReactiveBlePlatform extends _i1.Mock
_i2.Result<_i2.Unit,
_i2.GenericFailure<_i2.ClearGattCacheError>?>>);
@override
_i4.Future<int> readRssi(String? deviceId) => (super.noSuchMethod(
Invocation.method(
#readRssi,
[deviceId],
),
returnValue: _i4.Future<int>.value(0),
) as _i4.Future<int>);
@override
_i4.Stream<void> connectToDevice(
String? id,
Map<_i2.Uuid, List<_i2.Uuid>>? servicesWithCharacteristicsToDiscover,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ class MockReactiveBlePlatform extends _i1.Mock
_i2.Result<_i2.Unit,
_i2.GenericFailure<_i2.ClearGattCacheError>?>>);
@override
_i4.Future<int> readRssi(String? deviceId) => (super.noSuchMethod(
Invocation.method(
#readRssi,
[deviceId],
),
returnValue: _i4.Future<int>.value(0),
) as _i4.Future<int>);
@override
_i4.Stream<void> connectToDevice(
String? id,
Map<_i2.Uuid, List<_i2.Uuid>>? servicesWithCharacteristicsToDiscover,
Expand Down
8 changes: 8 additions & 0 deletions packages/flutter_reactive_ble/test/reactive_ble_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ class MockReactiveBlePlatform extends _i1.Mock
_i2.Result<_i2.Unit,
_i2.GenericFailure<_i2.ClearGattCacheError>?>>);
@override
_i4.Future<int> readRssi(String? deviceId) => (super.noSuchMethod(
Invocation.method(
#readRssi,
[deviceId],
),
returnValue: _i4.Future<int>.value(0),
) as _i4.Future<int>);
@override
_i4.Stream<void> connectToDevice(
String? id,
Map<_i2.Uuid, List<_i2.Uuid>>? servicesWithCharacteristicsToDiscover,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ interface BleClient {
fun observeBleStatus(): Observable<BleStatus>
fun requestConnectionPriority(deviceId: String, priority: ConnectionPriority):
Single<RequestConnectionPriorityResult>
fun readRssi(deviceId: String): Single<Int>
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Int> = currentConnection?.let { connection ->
when (connection) {
is EstablishedConnection -> connection.rxConnection.readRssi()
is EstablishConnectionFailure -> Single.error(Throwable(connection.errorMessage))
}
} ?: Single.error(IllegalStateException("Connection is not established"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -353,6 +353,21 @@ open class ReactiveBleClient(private val context: Context) : BleClient {
}
}.first(RequestConnectionPriorityFailed(deviceId, "Unknown failure"))

override fun readRssi(deviceId: String): Single<Int> =
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(
Expand Down
Loading

0 comments on commit a3e4148

Please sign in to comment.