-
Notifications
You must be signed in to change notification settings - Fork 3
RaftBLEManagerSysMod
Drives the device's Bluetooth Low Energy stack on top of NimBLE. Provides a BLE peripheral that exposes Raft's command/response GATT service (and optional standard services like Battery / Device Info / Heart Rate), and an optional BLE central role for scanning advertisements (e.g. BTHome) and feeding them into the device's bus subsystem.
BLEManager wraps the NimBLE host that ships with ESP-IDF and integrates it with the rest of Raft:
- It is a SysMod, so it participates in the standard
setup()/loop()cycle and is configured via SysType JSON under the keyBLEMan. - It registers a Comms Channel so command/response traffic from a connected BLE central is exchanged with the rest of the device using the same RICREST framing as WiFi/Serial.
- It runs its own GAP/GATT event handling (advertise → connect → MTU/PHY exchange → connection-parameter update) and exposes named values and REST endpoints for control.
The two roles can be enabled independently:
| Role | Config | Purpose |
|---|---|---|
| Peripheral |
peripheral: 1 (default) |
Advertise; accept one connection from a phone / browser / dongle; expose the command-response GATT service. |
| Central | central: 1 |
Scan continuously for advertisements (passive or active); decode known formats (e.g. BTHome) and forward to a Bus named by busConnName. Outbound connections from the central are not currently supported. |
Full reference: BLE Manager Settings.
Common keys at a glance:
The uuidCmdResp* triple defines the command/response service and characteristics (see GATT layout). Leave them at their defaults unless you have a reason to change them — RaftJS and the example WebUIs use the defaults.
In peripheral mode BLEManager performs the standard NimBLE flow:
- Initialise the host stack (once per boot).
- Register the Raft command/response service plus any standard services selected via
stdServices. - Start advertising using the configured
advIntervalMs, advertising name, manufacturer data, and filter UUID. - On connection, negotiate MTU and (where the central permits) the preferred connection interval, link-layer packet length and PHY.
- Pump inbound writes into the comms channel and dispatch outbound
CommsChannelMsgtraffic over notifications or indications (sendUseInd). - Track RSSI and connection state; expose them via named values and
getStatusJSON().
Only one connection at a time is supported in peripheral mode. The advertising name is built from the system's friendly name (or getSystemName() / getSystemUniqueString() if that is unset) — see BLEManager::getAdvertisingInfo().
The Raft command-response service has three characteristics:
| Characteristic | Properties | Direction | Purpose |
|---|---|---|---|
uuidCmdRespCommand |
Write, Write-without-response | Central → Device | Inbound RICREST frames (commands, file blocks, etc.). |
uuidCmdRespResponse |
Notify or Indicate | Device → Central | Outbound RICREST frames (responses, publishes). The transport mode is selected by sendUseInd. |
| (response, read) | Read | Central → Device (snapshot) | Last response value (rarely used by clients; the notify/indicate path is the primary one). |
The maximum payload per notification/indication is maxPktLen (≤ MTU − 3). Larger RICREST messages are framed and split by the comms layer, not at the BLE level. See Communications Stack Overview and RICREST Protocol for the wire format.
sendUseInd controls the choice of transport for outbound messages:
-
Indicate (
sendUseInd: 1, default) — every message is acknowledged at the BLE link layer; only one message can be in flight per connection event. Reliable but rate-limited by the connection interval. -
Notify (
sendUseInd: 0) — fire-and-forget; multiple notifications can leave per connection event so achievable throughput is much higher, at the cost of central-side ordering/loss handling. Raft additionally throttles notify mode withminMsBetweenSends(default 50 ms).
Any subset of the standard Bluetooth services can be enabled via the stdServices array:
| Service | Use |
|---|---|
DeviceInfo |
Manufacturer / model / firmware-version strings shown by phones and OS Bluetooth UIs. |
Battery |
Battery percentage. Source value is read from another SysMod's named-value via sysMod + namedValue. |
HeartRate |
Heart-rate measurement. Same named-value plumbing as Battery. |
Each entry chooses read / notify / indicate properties and an updateIntervalMs at which the underlying named-value is sampled. See BLE Manager Settings → Standard Services for the schema and example.
When central: 1, BLEManager configures NimBLE for scanning according to:
| Key | Meaning |
|---|---|
scanIntervalMs / scanWindowMs
|
NimBLE scan duty cycle. |
scanPassive |
Passive (no scan-response request) vs active. |
scanNoDup |
Deduplicate identical advertisements within a window. |
scanLimited |
Filter to limited-discoverable advertisers only. |
scanForSecs |
0 to scan forever (typical), or a time-limited scan. |
scanBTHome |
Recognise BTHome packets. |
Each advertisement is decoded by BLEAdvertDecoder. Recognised packets (BTHome v1/v2 today) are translated into a synthetic device record and pushed into the Bus named by busConnName via RaftBusDevicesIF, so they become indistinguishable from I²C-discovered devices to the rest of the application — they appear in Device Manager listings, are publishable on devjson / devbin, and are recordable by DataLogger.
Outbound connections (acting as a client to another peripheral) are not currently supported in Raft. Devices that broadcast their state (BTHome thermometers, hygrometers, motion sensors, etc.) are the intended use case.
BLEManager::addCommsChannels() registers a single channel called BLE with the comms core. The channel uses RICSerial framing by default and is the route by which:
- Inbound writes (commands, RICREST messages, file blocks) are decoded and dispatched to ProtocolExchange.
- Outbound responses and publish messages are framed and queued for transmission via notify / indicate.
Outbound queueing is handled by BLEGattOutbound, which enforces:
- A bounded queue of
outQSizemessages (default 30). - A reservation of
outQResvNonPubslots for non-publish messages — publishes are dropped when the queue is fuller thanoutQSize − outQResvNonPub. This protects command responses from being starved by high-rate publishing. - A 500 ms in-flight timeout (
outMsgsInFlightMs) after which the next message is sent regardless.
Other SysMods (and the standard BLE services) read live state from BLEManager via the named-value mechanism. The supported names are:
| Name | Meaning |
|---|---|
R / r
|
Latest cached RSSI in dBm (refreshed every 2 s while connected). |
C / c
|
1 if a central is connected, else 0. |
These are convenient for cross-SysMod logic — e.g. a power-management SysMod that switches a sensor to low-rate mode while no BLE central is connected.
Endpoints are added in BLEManager::addRestAPIEndpoints():
| Endpoint | Method | Purpose |
|---|---|---|
blerestart |
GET | Tear down and re-initialise the BLE stack. Useful after a configuration change without rebooting. |
bledisconnect |
GET | Force-disconnect the active central. Useful from automated test rigs. |
bleconfig |
POST | Apply a partial configuration update at runtime (e.g. tweak connIntvPrefMs). |
Request/response details follow the standard REST API conventions.
getStatusJSON() reports advertising name, connection state, RSSI, MTU, the negotiated connection parameters and the queue occupancy. getDebugJSON() adds counters from BLEManStats — frames in/out, indication ACK timeouts, queue rejections, etc. Visible at runtime via sysmoddebug/BLEMan if LogManager is enabled.
The achievable throughput from a Raft device over BLE is dominated by three knobs: connection interval, indication-vs-notify mode, and link-layer packet length / MTU. The trade-offs are non-obvious — the source notes in RaftSysMods/devdocs/BLE-publish-throughput-analysis.md measure the issue end-to-end with a 104 Hz sample stream; the summary below distils that analysis.
There are two layers that can refuse a publish:
-
CommsChannelManager::handleOutboundMessageOnChannelskips a publish if any non-publish message is already queued on the channel. Logged only whenDEBUG_OUTBOUND_PUBLISHis defined. -
BLEGattOutbound::isReadyToSendrejects a publish whenqueueCount + outQResvNonPub >= outQSize. With defaults (outQSize=30,outQResvNonPub=10) publishes can occupy at most 20 slots. Logged whenWARN_ON_PUBLISH_QUEUE_FULLis defined.
Both rejections are intentional — they protect command responses from being delayed behind a backlog of publishes — but they are silent unless those debug flags are enabled.
With sendUseInd=1, the BLE spec limits indications to one in flight at a time (a peripheral must wait for the ACK before the next indication can be queued). The configured outMsgsInFlightMax is therefore not exercised — the in-flight count is hard-gated to 1.
Each indication ACK takes 1–2 connection intervals, so:
| Connection interval | Indication round-trip | Max indication rate |
|---|---|---|
| 15 ms (default) | 15–30 ms | ~33–66 msg/s |
| 7.5 ms (BLE minimum) | 7.5–15 ms | ~66–133 msg/s |
| 50 ms (low-power) | 50–100 ms | ~10–20 msg/s |
For a typical devbin payload of ~330 B, that translates to roughly 10–66 KB/s in indication mode at the default interval. Light-sleep cannot run while a 7.5 ms interval is in use, so the minimum interval is unsuitable for battery-powered devices.
| Change | Throughput effect | Trade-off |
|---|---|---|
Lower connIntvPrefMs (e.g. 8) |
Roughly doubles indication rate | More power; central may override; battery life ↓ |
Switch publishes to notify (sendUseInd: 0) |
3–5× higher publish rate (multiple notifications per connection event) | No link-layer ACK; central must tolerate ordering / loss; lose minMsBetweenSends is then 50 ms by default — reduce it for fast streams |
| Request 2 M PHY (where supported on both ends) | ~1.5× from faster ACK round-trip | Both sides must support BLE 5 |
Increase outQSize / I²C bus ring size |
Buys headroom across stalls | Doesn't lift the ceiling — only delays overflow |
| Compress / decode payload firmware-side | Linearly reduces required throughput | Bigger code change; affects the firmware/JS decoder boundary |
If you are streaming high-rate data (sample buffers, multi-axis IMUs at hundreds of Hz), the practical recipe is: notify mode + 7.5 ms connection interval + DLE on (llPacketLengthPref: 251, default) + 2 M PHY where available. Reserve indication mode for command/response and lower-rate status topics.
| Parameter | Default | Source |
|---|---|---|
sendUseInd |
true |
BLEConfig.h |
connIntvPrefMs |
15 ms | BLEConfig.h |
mtuSize |
512 | BLEConfig.h |
maxPktLen |
500 | BLEConfig.h |
outQSize |
30 | BLEConfig.h |
outQResvNonPub |
10 | BLEConfig.h |
outMsgsInFlightMs |
500 | BLEConfig.h |
minMsBetweenSends |
50 (notify only) | BLEConfig.h |
llPacketLengthPref |
251 (DLE) | BLEConfig.h |
llPacketTimePref |
2500 µs | BLEConfig.h |
For deeper analysis with measured numbers see RaftSysMods/devdocs/BLE-publish-throughput-analysis.md.
- BLE Manager Settings — full configuration reference.
- Comms Channels — how the BLE channel slots into the comms core.
- Communications Stack Overview and RICREST Protocol — the wire format BLE carries.
- Registering as a Data Source — origin of the publish messages discussed in Throughput and tuning.
- OTA Update Flow — how firmware updates traverse this transport.
Getting Started
- Quick Start
- Architecture at a Glance
- Writing Your First SysMod
- Adding a Comms Channel
- Adding an I2C Device Type
- PlatformIO / Arduino
Scaffolding & Building
- Raft CLI
- SysTypes
- Top-Level SysType
- Build Process
- WebUI Build Pipeline
- File System
- Partitions & Flash
- Local Dev Libraries
- Library Developer Guide
Architecture
Built-in SysMods
- NetworkManager
- BLEManager
- WebServer
- MQTTManager
- SerialConsole
- CommandSerial
- CommandSocket
- CommandFile
- FileManager
- LogManager
- ESPOTAUpdate
- StatePublisher
- Remote Logging
- Data Source Registration
Comms & Protocols
- Stack Overview
- Comms Channels
- ProtocolExchange
- RICREST Protocol
- Real-Time Streams
- Adding REST Endpoints
- Built-in REST Endpoints
- File Download (OKTO)
- OTA Update Flow
Devices & Buses
- DeviceManager
- Device Manager REST API
- Device Factory & Classes
- Device Type Records
- Adding an I2C Device Type
- Device Data Publishing
- Data Logger
- I2C Bus
- I2C Device Scanning
- I2C ID & Polling
- MotorControl Overview
- MotorControl Config
- MotorControl Commands
Helpers
Reference