Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions bluetooth/src/commonMain/kotlin/Bluetooth.kt
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,15 @@ fun Flow<RemoteCharacteristic?>.value(): Flow<ByteArray> = distinctUntilChanged(
characteristic?.value() ?: emptyFlow()
}

/**
* Gets a ([Flow] of) the [ByteArray] value from a [Flow] of an [RemoteCharacteristic] or an empty [ByteArray] if data is unavailable
* This method will automatically subscribe/unsubscribe to the [RemoteCharacteristic] when the [Flow] is collected
* @return the [Flow] of the [ByteArray] value of the [RemoteCharacteristic] in the given [Flow], or an empty [ByteArray] if data is unavailable
*/
fun Flow<RemoteCharacteristic?>.valueOrEmpty(): Flow<ByteArray> = distinctUntilChanged().flatMapLatest { characteristic ->
characteristic?.value() ?: flowOf(byteArrayOf())
}

/**
* Gets a ([Flow] of) [T] value from a [Flow] of an [RemoteCharacteristic]
* This method will automatically subscribe/unsubscribe to the [RemoteCharacteristic] when the [Flow] is collected
Expand All @@ -641,6 +650,19 @@ fun <T> Flow<RemoteCharacteristic?>.value(deserializationStrategy: Deserializati
characteristic?.value(deserializationStrategy, bluetoothFormat) ?: emptyFlow()
}

/**
* Gets a ([Flow] of) [T] value from a [Flow] of an [RemoteCharacteristic] or `null` if data is unavailable
* This method will automatically subscribe/unsubscribe to the [RemoteCharacteristic] when the [Flow] is collected
* @param T the type of the data to receive
* @param deserializationStrategy the [DeserializationStrategy] to use to deserialize the [ByteArray] to [T]
* @param bluetoothFormat the [BluetoothFormat] to use to deserialize the [ByteArray] to [T]
* @return the [Flow] of the [T] value of the [RemoteCharacteristic] in the given [Flow] or `null` if data is unavailable
*/
fun <T> Flow<RemoteCharacteristic?>.valueOrNull(deserializationStrategy: DeserializationStrategy<T>, bluetoothFormat: BluetoothFormat = BluetoothFormat): Flow<T?> =
distinctUntilChanged().flatMapLatest { characteristic ->
characteristic?.value(deserializationStrategy, bluetoothFormat) ?: flowOf(null)
}

/**
* Gets a ([Flow] of) [T] value from a [Flow] of an [RemoteCharacteristic]
* This method will automatically subscribe/unsubscribe to the [RemoteCharacteristic] when the [Flow] is collected
Expand All @@ -651,6 +673,16 @@ fun <T> Flow<RemoteCharacteristic?>.value(deserializationStrategy: Deserializati
inline fun <reified T> Flow<RemoteCharacteristic?>.value(bluetoothFormat: BluetoothFormat = BluetoothFormat): Flow<T> =
value(bluetoothFormat.serializersModule.serializer(), bluetoothFormat)

/**
* Gets a ([Flow] of) [T] value from a [Flow] of an [RemoteCharacteristic]
* This method will automatically subscribe/unsubscribe to the [RemoteCharacteristic] when the [Flow] is collected
* @param T the type of the data to receive
* @param bluetoothFormat the [BluetoothFormat] to use to deserialize the [ByteArray] to [T]
* @return the [Flow] of the [T] value of the [RemoteCharacteristic] in the given [Flow]
*/
inline fun <reified T> Flow<RemoteCharacteristic?>.valueOrNull(bluetoothFormat: BluetoothFormat = BluetoothFormat): Flow<T?> =
valueOrNull(bluetoothFormat.serializersModule.serializer(), bluetoothFormat)

/**
* Gets a ([Flow] of) the [ByteArray] value from a [RemoteCharacteristic]
* This method will automatically subscribe/unsubscribe to the [RemoteCharacteristic] when the [Flow] is collected
Expand Down
175 changes: 150 additions & 25 deletions bluetooth/src/commonMain/kotlin/extensions/Accessors.kt
Original file line number Diff line number Diff line change
@@ -1,69 +1,194 @@
package com.splendo.kaluga.bluetooth.extensions

import com.splendo.kaluga.bluetooth.Characteristic
import com.splendo.kaluga.bluetooth.RemoteCharacteristic
import com.splendo.kaluga.bluetooth.RemoteDescriptor
import com.splendo.kaluga.bluetooth.RemoteService
import com.splendo.kaluga.bluetooth.UUIDException
import com.splendo.kaluga.bluetooth.characteristics
import com.splendo.kaluga.bluetooth.descriptors
import com.splendo.kaluga.bluetooth.device.ConnectableDevice
import com.splendo.kaluga.bluetooth.discoveredServices
import com.splendo.kaluga.bluetooth.serialization.BluetoothFormat
import com.splendo.kaluga.bluetooth.services
import com.splendo.kaluga.bluetooth.value
import com.splendo.kaluga.bluetooth.valueOrNull
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.serialization.DeserializationStrategy

/**
* Provides access to device data flow by service, characteristic and descriptor string uuids.
* Provides access to a [RemoteService] [Flow] by service string uuids.
* Only emits after services have been discovered.
* @param serviceUUID string service uuid representation
* @throws UUIDException.InvalidFormat if [serviceUUID] is not valid
* @return the [Flow] of the [com.splendo.kaluga.bluetooth.RemoteService] associated with [serviceUUID]. Flow throws [NoSuchElementException] if the service cannot be found after discovery.
*/
fun Flow<ConnectableDevice?>.serviceFlow(serviceUUID: String) = discoveredServices()[serviceUUID]

/**
* Provides access to a [RemoteService] [Flow] by service string uuids.
* Emits `null` if the service cannot be found.
* @param serviceUUID string service uuid representation
* @throws UUIDException.InvalidFormat if [serviceUUID] is not valid
* @return the [Flow] of the [RemoteService] associated with [serviceUUID] or `null` if the service is not available.
*/
fun Flow<ConnectableDevice?>.serviceOrNullFlow(serviceUUID: String) = services().getOrNull(serviceUUID)

/**
* Provides access to [RemoteService] by service string uuids.
* The method will suspend until services have been discovered.
* @param serviceUUID string service uuid representation
* @throws UUIDException.InvalidFormat if [serviceUUID] is not valid
* @throws NoSuchElementException if the service cannot be found after discovery.
* @return the [RemoteService] associated with [serviceUUID]
*/
suspend fun Flow<ConnectableDevice?>.service(serviceUUID: String) = serviceFlow(serviceUUID)
.first()

/**
* Provides access to [RemoteService] by service and characteristic string uuids or `null` if not available.
* @param serviceUUID string service uuid representation
* @param characteristicUUID string characteristic uuid representation
* @throws UUIDException.InvalidFormat
* @return the [RemoteService] associated with [serviceUUID] or `null` if not available
*/
fun Flow<ConnectableDevice?>.dataFlow(serviceUUID: String, characteristicUUID: String) = characteristicsFlow(serviceUUID, characteristicUUID).value()
suspend fun Flow<ConnectableDevice?>.serviceOrNull(serviceUUID: String) = serviceOrNullFlow(serviceUUID)
.first()

/**
* Provides access to characteristic's flow by service and characteristic string uuids.
* Provides access to [RemoteCharacteristic]'s flow by service and characteristic string uuids.
* Only emits after services have been discovered.
* @param serviceUUID string service uuid representation
* @param characteristicUUID string characteristic uuid representation
* @throws UUIDException.InvalidFormat
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
* @return the [Flow] of the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID]. Flow throws [NoSuchElementException] if the characteristic cannot be found after discovery.
*/
fun Flow<ConnectableDevice?>.characteristicsFlow(serviceUUID: String, characteristicUUID: String) = services().getOrNull(serviceUUID)
fun Flow<ConnectableDevice?>.characteristicFlow(serviceUUID: String, characteristicUUID: String) = serviceFlow(serviceUUID)
.characteristics()[characteristicUUID]
.filterNotNull()

/**
* Provides access to [Characteristic] by service and characteristic string uuids.
* Provides access to [RemoteCharacteristic]'s flow by service and characteristic string uuids.
* Emits `null` if the characteristic cannot be found.
* @param serviceUUID string service uuid representation
* @param characteristicUUID string characteristic uuid representation
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
* @return the [Flow] of the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID] or `null` if the characteristic is not available.
*/
fun Flow<ConnectableDevice?>.characteristicOrNullFlow(serviceUUID: String, characteristicUUID: String) = serviceOrNullFlow(serviceUUID)
.characteristics().getOrNull(characteristicUUID)

/**
* Provides access to [RemoteCharacteristic] by service and characteristic string uuids.
* The method will suspend if characteristic is not available.
* @param serviceUUID string service uuid representation
* @param characteristicUUID string characteristic uuid representation
* @throws UUIDException.InvalidFormat
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
* @throws NoSuchElementException if the characteristic cannot be found after discovery.
* @return the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID]
*/
suspend fun Flow<ConnectableDevice?>.characteristic(serviceUUID: String, characteristicUUID: String) = services().getOrNull(serviceUUID)
.characteristics()[characteristicUUID]
.filterNotNull()
suspend fun Flow<ConnectableDevice?>.characteristic(serviceUUID: String, characteristicUUID: String) = characteristicFlow(serviceUUID, characteristicUUID)
.first()

/**
* Provides access to descriptors's flow by service, characteristic and descriptor string uuids.
* Provides access to [RemoteCharacteristic] by service and characteristic string uuids or `null` if not available.
* @param serviceUUID string service uuid representation
* @param characteristicUUID string characteristic uuid representation
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
* @return the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID] or `null` if not available
*/
suspend fun Flow<ConnectableDevice?>.characteristicOrNull(serviceUUID: String, characteristicUUID: String) = characteristicOrNullFlow(serviceUUID, characteristicUUID)
.first()

/**
* Provides access to [RemoteDescriptor]'s flow by service, characteristic, and descriptor string uuids.
* Only emits after services have been discovered.
* @param serviceUUID string service uuid representation
* @param characteristicUUID string characteristic uuid representation
* @param descriptorUUID string descriptor uuid representation
* @throws UUIDException.InvalidFormat
* @throws UUIDException.InvalidFormat if [serviceUUID], [characteristicUUID], or [descriptorUUID] is not valid
* @return the [Flow] of the [RemoteDescriptor] associated with [serviceUUID], [characteristicUUID], and [descriptorUUID]. Flow throws [NoSuchElementException] if the descriptor cannot be found after discovery.
*/
fun Flow<ConnectableDevice?>.descriptorsFlow(serviceUUID: String, characteristicUUID: String, descriptorUUID: String) = services().getOrNull(serviceUUID)
.characteristics()[characteristicUUID]
fun Flow<ConnectableDevice?>.descriptorFlow(serviceUUID: String, characteristicUUID: String, descriptorUUID: String) = characteristicFlow(serviceUUID, characteristicUUID)
.descriptors()[descriptorUUID]
.filterNotNull()

/**
* Provides access to [RemoteDescriptor]'s flow by service, characteristic, and descriptor string uuids.
* Emits `null` if the descriptor cannot be found.
* @param serviceUUID string service uuid representation
* @param characteristicUUID string characteristic uuid representation
* @param descriptorUUID string descriptor uuid representation
* @throws UUIDException.InvalidFormat if [serviceUUID], [characteristicUUID], or [descriptorUUID] is not valid
* @return the [Flow] of the [RemoteDescriptor] associated with [serviceUUID], [characteristicUUID], and [descriptorUUID] or `null` if the descriptor is not available.
*/
fun Flow<ConnectableDevice?>.descriptorOrNullFlow(serviceUUID: String, characteristicUUID: String, descriptorUUID: String) =
characteristicOrNullFlow(serviceUUID, characteristicUUID)
.descriptors().getOrNull(descriptorUUID)

/**
* Provides access to [RemoteDescriptor] by service, characteristic and descriptor string uuids.
* The method will suspend if descriptor is not available.
* @param serviceUUID string service uuid representation
* @param characteristicUUID string characteristic uuid representation
* @param descriptorUUID string descriptor uuid representation
* @throws UUIDException.InvalidFormat
* @throws UUIDException.InvalidFormat if [serviceUUID], [characteristicUUID], or [descriptorUUID] is not valid
* @throws NoSuchElementException if the descriptor cannot be found after discovery.
* @return the [RemoteDescriptor] associated with [serviceUUID], [characteristicUUID], and [descriptorUUID]
*/
suspend fun Flow<ConnectableDevice?>.descriptor(serviceUUID: String, characteristicUUID: String, descriptorUUID: String) = services().getOrNull(serviceUUID)
.characteristics()[characteristicUUID]
.descriptors()[descriptorUUID]
.filterNotNull()
.first()
suspend fun Flow<ConnectableDevice?>.descriptor(serviceUUID: String, characteristicUUID: String, descriptorUUID: String) =
descriptorFlow(serviceUUID, characteristicUUID, descriptorUUID)
.first()

/**
* Provides access to device data flow by service and characteristic string uuids.
* Only emits after services have been discovered.
* @param serviceUUID string service uuid representation
* @param characteristicUUID string characteristic uuid representation
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
* @return the [Flow] of the [ByteArray] value of the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID]. Flow throws [NoSuchElementException] if the characteristic cannot be found after discovery.
*/
fun Flow<ConnectableDevice?>.dataFlow(serviceUUID: String, characteristicUUID: String) = characteristicFlow(serviceUUID, characteristicUUID).value()

/**
* Provides access to device data flow [T] by service and characteristic string uuids.
* Only emits after services have been discovered.
* @param T the type of the data to receive
* @param serviceUUID string service uuid representation
* @param characteristicUUID string characteristic uuid representation
* @param deserializationStrategy the [DeserializationStrategy] to use to deserialize the [ByteArray] to [T]
* @param bluetoothFormat the [BluetoothFormat] to use to deserialize the [ByteArray] to [T]
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
* @return the [Flow] of the [T] value of the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID]. Flow throws [NoSuchElementException] if the characteristic cannot be found after discovery.
*/
inline fun <reified T> Flow<ConnectableDevice?>.dataFlow(
serviceUUID: String,
characteristicUUID: String,
deserializationStrategy: DeserializationStrategy<T>,
bluetoothFormat: BluetoothFormat = BluetoothFormat,
) = characteristicFlow(serviceUUID, characteristicUUID).value(deserializationStrategy, bluetoothFormat)

/**
* Provides access to device data flow by service and characteristic string uuids.
* Emits and empty [ByteArray] if the service cannot be found.
* @param serviceUUID string service uuid representation
* @param characteristicUUID string characteristic uuid representation
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
* @return the [Flow] of the [ByteArray] value of the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID]. Emits an empty [ByteArray] if the characteristic is not available.
*/
fun Flow<ConnectableDevice?>.dataOrEmptyFlow(serviceUUID: String, characteristicUUID: String) = characteristicOrNullFlow(serviceUUID, characteristicUUID).value()

/**
* Provides access to device data flow [T] by service and characteristic string uuids.
* Emits `null` if the descriptor cannot be found.
* @param T the type of the data to receive
* @param serviceUUID string service uuid representation
* @param characteristicUUID string characteristic uuid representation
* @param deserializationStrategy the [DeserializationStrategy] to use to deserialize the [ByteArray] to [T]
* @param bluetoothFormat the [BluetoothFormat] to use to deserialize the [ByteArray] to [T]
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
* @return the [Flow] of the [T] value of the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID]. Emits `null` if the descriptor cannot be found.
*/
inline fun <reified T> Flow<ConnectableDevice?>.dataOrNullFlow(
serviceUUID: String,
characteristicUUID: String,
deserializationStrategy: DeserializationStrategy<T>,
bluetoothFormat: BluetoothFormat = BluetoothFormat,
) = characteristicOrNullFlow(serviceUUID, characteristicUUID).valueOrNull(deserializationStrategy, bluetoothFormat)
Loading