Examples
Code examples for SimpleBluez.
To learn how to use SimpleBluez, please refer to the examples provided in the repository.
For controllers that support extended advertising, the advertisement secondary channel can be selected before registering the advertisement:
auto advertisement = bluez.root_custom()->advertisement_add("reader");
advertisement->secondary_channel("2M");
adapter->register_advertisement(advertisement);Use adapter->supported_secondary_channels() to check whether BlueZ reports
support for "2M" on the selected adapter.
Local GATT read/write options
When exposing local GATT server objects, BlueZ passes per-client context in the
ReadValue and WriteValue options dictionary. The callbacks take
Characteristic::ValueOptions, which exposes fields such as device, mtu,
offset, link, type, and prepare-authorize as prepare_authorize in C++:
characteristic->set_on_read_value([characteristic](SimpleBluez::Characteristic::ValueOptions options) {
if (options.mtu.has_value()) {
std::cout << "Read MTU: " << *options.mtu << std::endl;
}
characteristic->value(SimpleBluez::ByteArray("hello"));
});
characteristic->set_on_write_value(
[](SimpleBluez::ByteArray value, SimpleBluez::Characteristic::ValueOptions options) {
if (options.mtu.has_value()) {
std::cout << "Write MTU: " << *options.mtu << std::endl;
}
});For user-generated/local GATT server characteristics, characteristic->mtu()
reflects the BlueZ GattCharacteristic1.MTU property and should not be treated
as a per-device server-side MTU. BlueZ provides the per-request server MTU in
ValueOptions::mtu for ReadValue and WriteValue callbacks when that field is
present.
Local GATT notify acquisition capability
By default, local GATT characteristics do not expose BlueZ's optional
NotifyAcquired property, so BlueZ uses the StartNotify and StopNotify
flow:
characteristic->set_on_notify([](bool notifying) {
std::cout << "Notifying: " << notifying << std::endl;
});The NotifyAcquired property can be exported or removed at runtime:
struct NotifyClient {
SimpleDBus::UnixSocket socket;
SimpleBluez::Characteristic::ValueOptions options;
};
std::vector<NotifyClient> notify_clients;
characteristic->set_on_acquire_notify(
[¬ify_clients](SimpleDBus::UnixSocket socket, SimpleBluez::Characteristic::ValueOptions options) {
std::cout << "Notify MTU: " << options.mtu.value_or(0) << std::endl;
notify_clients.emplace_back(NotifyClient{std::move(socket), options});
});
characteristic->enable_acquire_notify();
// Future BlueZ notification subscriptions can now discover NotifyAcquired.
characteristic->disable_acquire_notify();enable_acquire_notify() exports NotifyAcquired with a value of false.
disable_acquire_notify() invalidates the property, allowing BlueZ to fall back
to StartNotify and StopNotify for future subscriptions. Existing acquired
sockets are not closed automatically.
Acquired notify socket ownership
Each acquired socket is per BlueZ AcquireNotify call. SimpleBluez creates a
connected Unix socket pair, returns one end to BlueZ, and passes the
application-owned end to the callback. The application must move and store that
socket if it wants the acquired notify stream to remain alive after the callback
returns. If the socket is destroyed at the end of the callback, its file
descriptor is closed and BlueZ treats the acquired stream as released.
The socket passed to the callback is move-only. socket.fd() returns a borrowed
descriptor for polling or integration with an event loop; do not close that raw
descriptor directly while UnixSocket still owns it. Use socket.close() to
release the acquired stream, or socket.release() only when transferring the raw
descriptor to another owner that will close it.
Writing to the socket sends notification or indication payloads through BlueZ.
SimpleBluez creates acquired notify sockets in non-blocking mode, so send() can
write fewer bytes than requested or fail with EAGAIN / EWOULDBLOCK. User code
should poll socket.fd() for writability and retry as needed. Use options.mtu
as the negotiated MTU context for that acquired stream. SimpleBluez does not
chunk payloads, enforce MTU limits, remove closed sockets from user containers,
or track the lifetime of user-owned sockets after the callback returns.
For indications, poll socket.fd() for readability and call socket.receive()
to read confirmation bytes from BlueZ. A hangup or unrecoverable read/write
error means the acquired notify stream is gone; remove that socket from your
container and close it if it is still valid. Disabling acquire notify only removes
the optional NotifyAcquired property for future subscriptions; it does not
close sockets already handed to the application.
The MTU in options.mtu belongs to the acquired socket delivered in the same
callback. It is stable for that socket's lifetime. Store the MTU alongside the
socket, use it while writing to that socket, and discard both when the socket is
closed or reports an unrecoverable I/O error. Do not expect SimpleBluez to update
the MTU for a socket that has already been handed to application code.
When a central stops notifications or indications, BlueZ closes its end of the
acquired notify socket. SimpleBluez does not receive a separate StopNotify
callback for that fd-backed path. The application should detect the end of the
stream by polling the socket fd or by handling send() / receive() failures:
// Requires <cerrno>.
void notify_all(std::vector<NotifyClient>& clients, const SimpleBluez::ByteArray& payload) {
for (auto it = clients.begin(); it != clients.end();) {
auto& client = *it;
uint16_t mtu = client.options.mtu.value_or(23);
size_t max_payload = mtu > 3 ? mtu - 3 : 0;
if (payload.size() > max_payload) {
// Split or drop oversized payloads; SimpleBluez does not chunk them.
++it;
continue;
}
ssize_t bytes_sent = client.socket.send(payload.data(), payload.size());
if (bytes_sent == static_cast<ssize_t>(payload.size())) {
++it;
continue;
}
if (bytes_sent < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
// The non-blocking socket is not ready yet. Poll for POLLOUT and retry later.
++it;
continue;
}
// HUP, disconnect, central unsubscribe, or another hard socket error.
it = clients.erase(it);
}
}For event-loop based code, monitor each socket.fd() for POLLHUP, POLLERR,
or POLLNVAL and remove that NotifyClient when any of those flags appear.
Monitor for POLLOUT before retrying a payload that previously failed with
EAGAIN / EWOULDBLOCK. For indications, also monitor for POLLIN and call
socket.receive() to read BlueZ's confirmation byte.
