BLE connections and service discovery
Availability: Linux and embedded runtimes, on a node with Bluetooth hardware
A connection links a cell to a Bluetooth peripheral. It carries the peripheral's address and the GATT services and characteristics found while connecting.
When to use
Use a connection when a cell has to read, write, or subscribe to a peripheral's characteristics.
Scan first when the address is not known yet.
Operations
- Start a connection to an address, naming a handler for success and one for failure.
- Look up a characteristic by its service UUID and its own UUID.
- Read the connected peripheral's address.
- Disconnect.
Example
use myrmic_sdk::ble::{uuid128, Address, Connection, Disconnect, Uuid};
use myrmic_sdk::types::ble::DisconnectReason;
use myrmic_sdk::{Callback, InMemory, Metadata};
/// Nordic NUS service, and the characteristic the peripheral sends on.
const NUS_SERVICE: Uuid = uuid128!("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
const TX_CHAR: Uuid = uuid128!("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
static CONNECTION: InMemory<Option<Connection>> = InMemory::empty();
fn connect(address: Address) -> myrmic_sdk::Result {
myrmic_sdk::ble::connect(address)
.on_connected(Callback::of::<connected>())
.on_disconnected(Callback::of::<disconnected>())
// Returns once the attempt has started. The outcome arrives at one of
// the two handlers above.
.initiate()?;
Ok(())
}
#[myrmic_sdk::cmd]
fn connected(_md: Metadata, connection: Connection) -> myrmic_sdk::Result {
// A characteristic is named by two UUIDs: its service, and its own.
let _characteristic = connection
.characteristic(NUS_SERVICE, TX_CHAR)
.ok_or("the peripheral has no such characteristic")?;
// Reads, writes, and subscriptions all need this, so it is kept.
CONNECTION.with(|slot| *slot = Some(connection))?;
Ok(())
}
#[myrmic_sdk::cmd]
fn stop(_md: Metadata) -> myrmic_sdk::Result {
// This does not trigger the `on_disconnected` handler.
if let Some(connection) = CONNECTION.with(Option::take)? {
connection.disconnect()?;
}
Ok(())
}
#[myrmic_sdk::cmd]
fn disconnected(_md: Metadata, disconnect: Disconnect) -> myrmic_sdk::Result {
match disconnect.reason() {
// The attempt never got a link.
DisconnectReason::ConnectionFailed => myrmic_sdk::warn!("could not connect")?,
// The peripheral closed the link.
DisconnectReason::RemoteClosed => myrmic_sdk::warn!("the peripheral left")?,
// This node closed it, without the cell asking.
DisconnectReason::LocalClosed => myrmic_sdk::warn!("the node closed the link")?,
other => myrmic_sdk::warn!("disconnected: {other:?}")?,
}
CONNECTION.with(|slot| *slot = None)?;
Ok(())
}
Behavior
Normal
Connecting does not block the cell's handler. The call returns immediately and the outcome arrives later: the specified success handler runs when the link is up and its services are ready, and the specified failure handler runs when the attempt fails or a live link drops.
The services and characteristics are found while connecting, and a lookup searches only those.
Errors
Starting fails when either handler is missing, and when the runtime cannot use the Bluetooth adapter.
A failure after that point reaches the failure handler with a reason: the attempt failed, the peripheral closed the link, or the cell's own node closed it.
Limits
Letting go of a connection does not disconnect the peripheral.
A connection stops working when the runtime restarts.
The embedded runtime allows one connection at a time on a node, and refuses another. The Linux runtime allows any number.
On the embedded runtime, starting a scan closes an open connection, and the failure handler runs with the reason that the cell's own node closed it.
Some runtimes require a scan before a connection, and refuse a direct attempt. Scanning first works everywhere.
API documentation
For every connection method and disconnect reason, see connect, ConnectBuilder, Connection and Disconnect.