diff --git a/README.md b/README.md index 5127f62..aebce35 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![](https://jitpack.io/v/neoBortx/SimpleBleClient.svg)](https://jitpack.io/#neoBortx/SimpleBleClient) + # SimpleBleClient SimpleBleClient is an Android library designed to simplify Bluetooth Low Energy (BLE) operations, providing a straightforward and coroutine-based API for BLE device interaction. @@ -7,29 +9,113 @@ SimpleBleClient is an Android library designed to simplify Bluetooth Low Energy - Easy connection and communication with BLE devices. - Asynchronous operations using Kotlin coroutines. - Search and filter BLE devices based on services. -- Read and write data to BLE devices. +- Read and write data from/to BLE devices. - Subcribe to connection status changes. +- Subcribe to characteristics changes ## Gradle -TBD +Add Jitpack repository to your root build.gradle at the end of repositories: -## Ussage +```groovy +repositories { + mavenCentral() + maven { url 'https://jitpack.io' } +} +``` -First you need to initiaze the client +Add the dependency +```groovy +dependencies { + implementation 'com.github.neoBortx:SimpleBleClient:0.0.4' +} +``` + + +## Initialization + +First you need to build the client, some behaviors of the BLE client can be configured during the building procedure ```kotlin - val simpleBleClient = SimpleBleClientBuilder.build(context) + companion object { + + /** + * Number of messages to store in incoming message buffer, if the buffer is full, the oldest message will be + * removed + * Default 1 + */ + const val MESSAGE_BUFFER_SIZE = 20 + + /** + * BLE operation Timeout. If the operation (read, write, subscription or connection) takes longer than this an + * exception will be thrown + * Default 7000 milliseconds + */ + + const val OPERATION_TIMEOUT_MILLIS = 8000L + + /** + * The duration of the timeout for scanning BLE devices + * Default 10000 milliseconds + */ + + const val SCAN_PERIOD_MILLIS = 30000L + + /** + * The number of messages to store in the incoming message buffer to new consumer of the incoming message flow + * Default 0 + **/ + const val MESSAGE_BUFFER_RETRIES = 0 + } + + /** + * Depending on the protocol used by the device, the message received from the device can be fragmented or may + * require a special treatment. + * + * BLE operations are asynchronous but you only can perform one in a time. When you're expecting a fragmented + * messages, you need to wait for the last message to be received before sending the next operation. + * Because this is not straightforward, this interface is used to + * encapsulate the logic to handle this kind of messages in lower layers for you. + * + * You should implement your own and pass it to the SimpleBleClientBuilder. + * + * Default null + */ + private val messageProcessor = BleNetworkMessageProcessorImpl() + + private val simpleBleClient = SimpleBleClientBuilder() + .setMessageBufferRetries(MESSAGE_BUFFER_RETRIES) + .setMessageBufferSize(MESSAGE_BUFFER_SIZE) + .setOperationTimeOutMillis(OPERATION_TIMEOUT_MILLIS) + .setScanPeriodMillis(SCAN_PERIOD_MILLIS) + .setMessageProcessor(messageProcessor) + .build(context) ``` -Before to connecto to the device if you don't now the MAC of the device, you should call `getDevicesByService method: + +## Search devices + +You can retrieve the list of all BLE devices detected by the Android phone in this moment + +```kotlin +CoroutineScope(Dispatchers.IO).launch { + val deviceFlow = simpleBleClient.deviceSeeker.getDevicesNearby() + deviceFlow.collect { bluetoothDevice -> + // Handle each found device + // `bluetoothDevice` is an instance of BluetoothDevice + } +} +``` + +Or filter by name and/or service UUID ```kotlin CoroutineScope(Dispatchers.IO).launch { val serviceUUID = UUID.fromString("your-service-uuid") + val deviceName = "MyCamera" - val deviceFlow = simpleBleClient.getDevicesByService(serviceUUID) + val deviceFlow = simpleBleClient.deviceSeeker.getDevicesNearby(serviceUUID, deviceName) deviceFlow.collect { bluetoothDevice -> // Handle each found device // `bluetoothDevice` is an instance of BluetoothDevice @@ -37,9 +123,20 @@ CoroutineScope(Dispatchers.IO).launch { } ``` -This method search only devices that publish a service with the given UUID. +This function returns a cold flow with the list of all detected devices. When the timeout ends, the flow will be closed. + +During the search operation, you can stop the procedure anytime: + +```kotlin +CoroutineScope(Dispatchers.IO).launch { + simpleBleClient.deviceSeeker.stopSearchDevices() +} +``` + -Then with the MAC of the device you can start the connection procedure +## Connection + +Once you have the MAC address of the device you can start the connection procedure ```kotlin CoroutineScope(Dispatchers.IO).launch { @@ -50,47 +147,104 @@ CoroutineScope(Dispatchers.IO).launch { To monitor connection status changes, you can subscribe to the status flow and collect the updates in a coroutine: ```kotlin -CoroutineScope(Dispatchers.Main).launch { - val connectionStatusFlow = simpleBleClient.subscribeToConnectionStatusChanges() +val connectionStatusFlow = simpleBleClient.subscribeToConnectionStatusChanges().collect { status -> + // Handle the connection status update + // `status` could represent different states like connected, disconnected, connection and disconnecting. +} +``` - connectionStatusFlow.collect { status -> - // Handle the connection status update - // `status` could represent different states like connected, disconnected, connection and disconnecting. - } +In this example, the collect function of the Flow is used to receive updates about the connection status. The possible states are: +- CONNECTED +- DISCONNECTED +- CONNECTING +- DISCONNECTING +- UNKNOWN + + +Also, you can disconnect the BLE device anytime: + +```kotlin +CoroutineScope(Dispatchers.IO).launch { + bleClient.connection.disconnect() } ``` -In this example, the collect function of the Flow is used to receive updates about the connection status. It's essential to collect the Flow in a coroutine scope. Since status updates typically result in UI changes, Dispatchers.Main is used. Adjust the dispatcher and handling logic according to your specific needs and application architecture. +## Subscribe to characteristics changes -To get or send data from/to the device, you must subcribe first to the charactaristics. List librray is prepared to subcribe to all characteristics marked as Noticeable or Indictable in the BLE device. You could filter them passing a list characteristic UUID to monitor. +Before you get or send data from/to the device, you must first subscribe to its characteristics. This is mandatory and without making the subscription you won't be able to read any data sent from the BLE device. +So, you must compose a list of characteristics that you want to observe, like this: ```kotlin -CoroutineScope(Dispatchers.Main).launch { +CoroutineScope(Dispatchers.IO).launch { val characteristicsUUIDs = listOf( UUID.fromString("your-characteristic-uuid-1"), UUID.fromString("your-characteristic-uuid-2") ) - val isSuccess = simpleBleClient.subscribeToCharacteristicChanges(characteristicsUUIDs) + val isSuccess = simpleBleClient.subscription.subscribeToCharacteristicChanges(characteristicsUUIDs) } ``` -Note: At this moment this library does not support realtime charaxcteristic monitoring, you have to check its status by calling readData -Once the subcriptions are configured you can read data like this: +If you want to handle incoming messages in a reactive way using flows, you must invoke subscribeToIncomeMessages, which will return a hot flow with all incoming messages. + +```kotlin +val connectionStatusFlow = simpleBleClient.subscription.subscribeToIncomeMessages().collect { status -> + // Handle the connection status update + // `status` could represent different states like connected, disconnected, connection and disconnecting. +} +``` + + +## Read characteristics + +Note: You must subscribe to the characteristics changes before try to read it. + +This is quite straightforward; you must pass the characteristic UUID and the service that handles this characteristic to the readData function. +The value of the characteristic will be returned as a result of these functions. Also, this message can be handled by collecting the flow given by subscribeToIncomeMessages. +Important: To manage fragmented characteristics, you should implement the interface BleNetworkMessageProcessor and pass it to SimpleBleClientBuilder at initialization time. + ```kotlin CoroutineScope(Dispatchers.IO).launch { val serviceUUID = UUID.fromString("your-service-uuid") val characteristicUUID = UUID.fromString("your-characteristic-uuid") - val result = simpleBleClient.readData(serviceUUID, characteristicUUID) + val result = simpleBleClient.reader.readData(serviceUUID, characteristicUUID) // Process the result } ``` -And write data: + +### Write characteristics + +You can perform two different write operations: + +### With response + +In this case, you send some data to write to the BLE device, and it responds to you with some value, like an ACK or KO code. This operation returns only when the reponse +data has been processsed and will be included in the return object (BleNetworkMessage). + +You must send to the library the characteristic UUID, the UUID of the service that handles the characteristic, and the byteArray of data saved in the BLE device. + +```kotlin +CoroutineScope(Dispatchers.IO).launch { + val serviceUUID = UUID.fromString("your-service-uuid") + val characteristicUUID = UUID.fromString("your-characteristic-uuid") + val dataToSend = "Hello BLE".toByteArray() + + val result = simpleBleClient.writer.sendDataWithResponse(serviceUUID, characteristicUUID, dataToSend) + // Handle the result +} +``` + +The returned data is also sent to the hot flow given by the function subscribeToIncomeMessages. + +## Without response + +Fire and forget operation. It Just sent data to the BLE device without expecting any response. +You must send to the library the characteristic UUID, the UUID of the service that handles the characteristic, and the byteArray of data saved in the BLE device. ```kotlin CoroutineScope(Dispatchers.IO).launch { @@ -103,7 +257,6 @@ CoroutineScope(Dispatchers.IO).launch { } ``` -Remember to replace "Device_MAC_Address", "your-service-uuid", and "your-characteristic-uuid" with actual values relevant to your BLE device and services. ## License