Skip to content

Commit

Permalink
Update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
neoBortx authored Feb 2, 2024
1 parent 4258899 commit ef066c7
Showing 1 changed file with 177 additions and 24 deletions.
201 changes: 177 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -7,39 +9,134 @@ 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
}
}
```

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 {
Expand All @@ -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 {
Expand All @@ -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

Expand Down

0 comments on commit ef066c7

Please sign in to comment.