Profile
A class representing a Bluetooth LE profile.
The profiles are intended to separate the underlying GATT services and characteristics and the application logic and device API. For example, the profile can expose methods to turn a light ON or OFF, while internally it would use Bluetooth LE connection.
Use Peripheral.profile to register a profile on a peripheral.
Example
Simple profile
A profile based on a single GATT service extends Simple.
Profile definition
/**
* API of the profile.
*/
interface LedButton {
/** The current button state on the DK. */
val buttonState: StateFlow<Boolean>
/** The LED state. */
val ledState: MutableStateFlow<Boolean>
}
class LedButtonProfile: Profile.Simple(
serviceUuid = SERVICE_UUID,
name = "LBS",
), LedButton {
companion object {
val SERVICE_UUID = Uuid.parse("00001523-1212-efde-1523-785feabcd123")
val BUTTON_CHARACTERISTIC_UUID = Uuid.parse("00001524-1212-efde-1523-785feabcd123")
val LED_CHARACTERISTIC_UUID = Uuid.parse("00001525-1212-efde-1523-785feabcd123")
}
// GATT characteristics.
private lateinit var buttonCharacteristic: RemoteCharacteristic
private lateinit var ledCharacteristic: RemoteCharacteristic
// Public API.
override val buttonState: StateFlow<Boolean>
field = MutableStateFlow(false)
override val ledState: MutableStateFlow<Boolean> = MutableStateFlow(false)
// Implementation.
override fun prepare(peripheral: Peripheral<*, *>, service: RemoteService) {
// This should always pass.
require(service.uuid == SERVICE_UUID)
// Obtain characteristics from the service.
buttonCharacteristic = service.characteristics.first { it.uuid == BUTTON_CHARACTERISTIC_UUID }
ledCharacteristic = service.characteristics.first { it.uuid == LED_CHARACTERISTIC_UUID }
// Validate properties.
require(buttonCharacteristic.isSubscribable()) { "Button characteristic must be subscribable." }
require(ledCharacteristic.isWritable()) { "LED characteristic must be writable." }
}
override suspend fun CoroutineScope.initialize(peripheral: Peripheral<*, *>) {
// Subscribe to button characteristic.
buttonCharacteristic
.subscribe()
.map { value -> value.singleOrNull() == 1.toByte() }
.onEach { isPressed -> buttonState.update { isPressed } }
.launchIn(this)
// Read current Button state.
try {
val currentState = buttonCharacteristic.read()
buttonState.update { currentState.singleOrNull() == 1.toByte() }
} catch (e: OperationFailedException) {
println("Reading button characteristic failed: ${e.message}")
}
// Handle LED state updates.
ledState
.map { isOn -> byteArrayOf(if (isOn) 1 else 0) }
.onEach { value ->
try {
ledCharacteristic.write(value)
} catch (e: OperationFailedException) {
println("Writing LED characteristic failed: ${e.message}")
}
}
.launchIn(this)
}
}Usage
val api: LedButton = LedButtonProfile()
.also { peripheral.profile(it) }Multiservice profile
A profile based on multiple GATT services, some of which may be optional, extends MultiService.
Profile definition
/**
* API of the profile.
*/
interface Proximity {
/** Whether the peripheral also exposes the optional Immediate Alert Service. */
val isImmediateAlertSupported: Boolean
/** Writes the given alert level to the Link Loss Service. */
suspend fun setLinkLossAlertLevel(level: Int)
}
class ProximityProfile : Profile.MultiService(
requiredServiceUuids = listOf(LINK_LOSS_SERVICE_UUID),
optionalServiceUuids = listOf(IMMEDIATE_ALERT_SERVICE_UUID),
name = "Proximity",
), Proximity {
companion object {
val LINK_LOSS_SERVICE_UUID = Uuid.parse("00001803-0000-1000-8000-00805f9b34fb")
val IMMEDIATE_ALERT_SERVICE_UUID = Uuid.parse("00001802-0000-1000-8000-00805f9b34fb")
val ALERT_LEVEL_UUID = Uuid.parse("00002a06-0000-1000-8000-00805f9b34fb")
const val HIGH_ALERT = 2
}
// GATT characteristics.
private lateinit var linkLossAlertLevel: RemoteCharacteristic
private var immediateAlertLevel: RemoteCharacteristic? = null
// Public API.
override val isImmediateAlertSupported: Boolean
get() = immediateAlertLevel != null
// Implementation.
override fun prepare(peripheral: Peripheral<*, *>, services: List<RemoteService>) {
// Link Loss Service is required.
val linkLossService = services.first { it.uuid == LINK_LOSS_SERVICE_UUID }
linkLossAlertLevel = linkLossService.characteristics.first { it.uuid == ALERT_LEVEL_UUID }
require(linkLossAlertLevel.isWritable()) { "Alert Level characteristic must be writable." }
// Immediate Alert Service is optional - only present if the peripheral supports it.
immediateAlertLevel = services.firstOrNull { it.uuid == IMMEDIATE_ALERT_SERVICE_UUID }
?.characteristics?.first { it.uuid == ALERT_LEVEL_UUID }
}
override suspend fun CoroutineScope.initialize(peripheral: Peripheral<*, *>) {
// Set the Link Loss Alert Level to High as soon as the profile is initialized.
setLinkLossAlertLevel(HIGH_ALERT)
}
override suspend fun setLinkLossAlertLevel(level: Int) {
linkLossAlertLevel.write(byteArrayOf(level.toByte()))
}
}Usage
val api: Proximity = ProximityProfile()
.also { peripheral.profile(it) }See also
Inheritors
Constructors
Properties
Functions
Notifies the profile implementation that service discovery has failed.
This method should initialize the profile.
Validates and prepares the services to be used in initialize.
Notifies the profile implementation, that the peripheral does not support the required profile services.