RadBLE is the shared ESP32 implementation and machine-readable contract for the Research and Desire BLE Control Protocol v1. It gives OSSM, RADR, DTT, and LKBX one wire language while keeping product behavior in each firmware repository.
Pin an immutable release in lib_deps:
lib_deps =
https://github.com/researchanddesire/rad-ble.git#v1.0.0The library requires Arduino on ESP32, NimBLE-Arduino 2.4.0, and ArduinoJson 7.4 or newer.
Every server declares identity, capabilities, enabled optional GATT channels, resources, and callbacks. Core protocol, catalog, name, identity, request, response, state, essential-state, and event characteristics are always available. Everything else is opt-in through Config::channels.
const radble::Config config = {
.identity = {
.deviceType = "EXAMPLE",
.deviceName = "EXAMPLE",
.serviceUuid = "522b443a-4558-414d-0001-420badbabe69",
.firmwareVersion = "1.0.0",
.build = "release",
.partitionLayout = nullptr,
},
.capabilities = radble::CAP_BUTTON | radble::CAP_SENSOR_STREAM,
.channels = radble::CHANNEL_BUTTON | radble::CHANNEL_SENSOR_STREAM,
.resources = resources,
.resourceCount = resourceCount,
.callbacks = {
.commandHandler = handleCommand,
.snapshotHandler = snapshot,
.otaDataHandler = nullptr,
.otaSafetyHandler = nullptr,
.leaseReleaseHandler = nullptr,
.streamSafetyHandler = nullptr,
},
.context = nullptr,
};Invalid combinations are rejected during Server::begin with a specific [RAD BLE] invalid config: serial message. A channel that represents a capability must have the matching capability bit; filesystem OTA requires application OTA.
Products sharing State with a legacy read contract may set the optional
callbacks.readSnapshotHandler. It is called only for Surface::State; its
full JSON receives the usual state metadata and is stored for GATT reads.
snapshotHandler continues to supply compact notifications and state.read
responses. Forward the State characteristic's onSubscribe callback to
server.onStateSubscribe(connectionHandle, subValue) and all disconnects to
server.onDisconnect(connectionHandle). Use server.publishStateNotification
for additional State notifications: it sends only to subscribed connections and
never replaces the stored read value. Other asynchronous writers must not put
partial JSON or command acknowledgments in that characteristic. Products that
leave readSnapshotHandler unset retain the existing behavior.
protocol/rad-ble-v1.json is authoritative for product service UUIDs, characteristic suffixes and properties, operations, error codes, and size limits. Generated C++ and TypeScript constants keep firmware and clients aligned.
Regenerate or verify the checked-in C++ header:
python3 scripts/generate_protocol.py
python3 scripts/generate_protocol.py --checkGenerate a TypeScript module for a client repository:
python3 scripts/generate_protocol.py --typescript /path/to/ble.generated.tsReleases follow semantic versioning. Protocol-compatible additions use a minor release; breaking C++ or wire changes use a major release. Firmware consumers should always pin a tag.