Communicate with peripherals

This guide teaches you how to communicate with a Bluetooth peripheral using AbleLib.

Before you start, follow the introductory steps in this guide to make sure AbleLib is set up properly, and that your phone has all the necessary components and permissions.

AbleLib wraps all the peripheral device communication into an AbleComm instance. It manages the device connection, the connection state, as well as sending and receiving data, retries on lost transmissions, etc. You can just focus on what matters - getting data in and out of the peripheral.

As usual, the Android part of the SDK has both coroutine and callback support for AbleComm. This section covers the coroutine part as it's simpler to write and understand. Check out the advanced section for more info on the callback approach.

The first step is to obtain an AbleDevice that you wish to communicate with. You can create it yourself, read it from AbleDeviceStorage, get it at the end of a scanning-pairing sequence, it doesn't matter. Then, create the comm object with comm:

val device: AbleDevice = ... // obtain it somehow
val comm = device.comm

The second step is to connect to the device with, you guessed it, the connect method:

comm.connect()

After that, you're ready to communicate with your device. You can:

Discover services and their characteristics
 val characteristic = comm.discoverServices().first { service -> service.uuid == UUID_DEVICE_INFORMATION_SERVICE }
                .getCharacteristic(UUID_DEVICE_SERIAL_NUMBER_CHARACTERISTIC)
Read characteristics
val data = comm.readCharacteristic(characteristic).value
Write data to characteristics and receive results
val command = byteArrayOf(0x04, 0x01)
val result = comm.writeCharacteristic(characteristic, command).value
Mark characteristics to notify or indicate value change
comm.setNotifyValue(true, characteristic)
Write and read descriptors
val descriptor = comm.writeDescriptor(characteristic, characteristic.getConfigurationDescriptor(), false)
comm.readDescriptor(descriptor)

Be aware that all the comm methods in coroutine mode can throw exceptions, which need to be handled properly with try-catch blocks.

Advanced

Coroutine comm internally relies AbleCommCallback variant of AbleComm, which offers slightly more granular control over the communication process. The easiest way to use it is via the AbleCommBuilder pattern:

val device: AbleDevice = ... // obtain the device you want to communicate with somehow
val comm = device.asyncComm()
    .onConnectionStateChanged { newState, status ->
      // respond to connection state changes - connections, disconnects, etc
    }
    .onServicesDiscovered { services -> 
      // here's where the services will appear once you trigger discoverServices
    }
    .onCharacteristicRead { characteristic -> 
      // called after comm.readCharacteristic
    }
    .onCharacteristicChanged { characteristic -> 
      // called when a characteristic for which setNotifyValue was called changes its value
    }
    .onDescriptorRead { descriptor ->
      // called after comm.readDescriptor
    }
    .onError { error ->

    }
    .connect()

If some bits are still unclear, check out the demo apps (Android and iOS).