A no_std-compatible Bluetooth Low Energy (BLE) stack for embedded Rust, targeting both bare-metal (Embassy) and Linux/desktop (Tokio) environments.
┌──────────────────────────────────────────────┐
│ Application │
│ (implements BleHostObserver) │
├──────────────────────────────────────────────┤
│ bletio-host │
│ Type-state machine, observer pattern, │
│ advertising structures, connection mgmt │
├──────────────────────────────────────────────┤
│ bletio-hci │
│ HCI command/event encoding & parsing, │
│ controller flow control, timeouts │
├──────────────────────────────────────────────┤
│ bletio-utils │
│ Const-generic buffers, LE integer encoding, │
│ bitfield arrays │
├──────────────────────────────────────────────┤
│ HciDriver trait (you implement) │
│ UART / USB / SPI transport to controller │
├──────────────────────────────────────────────┤
│ BLE Controller (hardware) │
└──────────────────────────────────────────────┘
| Crate | Purpose |
|---|---|
bletio-utils |
Zero-allocation byte buffers, little-endian encoding, bitflag arrays |
bletio-hci |
Host-Controller Interface layer: command encoding, event parsing, flow control |
bletio-host |
Host layer: typed state machine, advertising, scanning, connections, observer pattern |
- Peripheral — Advertise, accept connections, update connection parameters
- Central — Scan, initiate connections, update connection parameters
- Observer — Passive scanning of advertising reports
- Broadcaster — Non-connectable advertising
- 25+ LE controller commands implemented per Bluetooth Core Specification v4.2+
- Nom-based zero-copy packet parsing for commands and events
- Controller flow control (
num_hci_command_packetstracking) - 1-second command timeouts
- Dual async runtime support: Tokio (desktop/Linux) and Embassy (bare-metal)
- Full event buffering with a
heapless::Vec(4 events) - Exhaustive error model: 50+ error variants covering all validation failures
- Command opcodes, error codes, and event codes via
num_enumwith catch-all variants
- Typed state machine — compile-time prevention of invalid API calls:
State Available operations InitialAutomatic setup only StandbyStart advertising, scanning, or connecting; manage filter accept list; create random address AdvertisingStop advertising ScanningStop scanning InitiatingCancel connection ConnectedCentralDisconnect, update connection parameters, send ACL data ConnectedPeripheralDisconnect, update connection parameters, send ACL data - Observer pattern —
BleHostObservertrait with 7 callbacks and sensible defaults - Data plane — Send/receive ACL data packets on connections; ATT PDU encoding/decoding (16 PDU types)
- Event loop —
BleDevice::run()drives the complete lifecycle - Automatic TX power and appearance insertion in advertising data
- Builder patterns for all parameter types
20+ AD structure types supported:
| Type | AD Type Code |
|---|---|
| Flags | 0x01 |
| Service UUIDs (16, 32, 128-bit, Incomplete/Complete list) | 0x02–0x07 |
| Local Name (Shortened/Complete) | 0x08/0x09 |
| TX Power Level | 0x0A |
| Peripheral Connection Interval Range | 0x12 |
| Service Solicitation (16, 32, 128-bit) | 0x14/0x1F/0x15 |
| Service Data (16, 32, 128-bit) | 0x16/0x20/0x21 |
| Appearance | 0x19 |
| Advertising Interval | 0x1A |
| Public/Random Target Address | 0x17/0x18 |
| LE Supported Features | 0x27 |
| URI | 0x24 |
| Manufacturer Specific Data | 0xFF |
Auto-generated from Bluetooth SIG sources:
- Company Identifiers
- Service UUIDs (16-bit)
- Appearance Values
- AD Types
- URI Schemes (provisioned)
| Feature | Purpose | Use Case |
|---|---|---|
tokio (default) |
Tokio async runtime | Linux, macOS, desktop testing |
embassy |
Embassy async runtime | Bare-metal embedded (nRF, STM32, ESP32) |
defmt |
Defmt logging (embedded) | Wire-format logging for probe-run/probe-rs |
log |
Standard log crate |
Desktop/host-platform diagnostics |
tokio/embassy are mutually exclusive. defmt/log are mutually exclusive. All provide HciDriver::with_timeout() via the WithTimeout trait.
- Zero heap allocations in core logic
heapless::Vecfor event buffering- Const-generic
Buffer<CAP>for packet construction defmtsupport for embedded logging (defmtfeature flag)logsupport for host-platform diagnostics (logfeature flag)embassy-timefor bare-metal timeouts
| Platform | Status | Notes |
|---|---|---|
| Linux (x86_64, aarch64) | ✅ Supported | tokio + HCI sockets; bletio CLI tool |
| macOS | ✅ Builds | tokio runtime; HCI driver needs platform-specific transport |
| nRF52840 / nRF5340 | ✅ Supported | embassy runtime; UART HCI driver |
| ESP32-C3 / ESP32-S3 | Should work with embassy + UART HCI driver |
|
| STM32WB | Should work with embassy + UART/SPI HCI driver |
|
| Raspberry Pi | ✅ Supported | Linux via hciattach; HCI socket or UART HCI driver |
All core crates build for any target with no_std support. Platform-specific
code is limited to the HciDriver transport implementation and the CLI tool.
[dependencies]
bletio-host = { git = "https://github.com/themactep/bletio" }
bletio-hci = { git = "https://github.com/themactep/bletio" }
bletio-utils = { git = "https://github.com/themactep/bletio" }For embedded use with defmt:
bletio-host = { git = "https://github.com/themactep/bletio", default-features = false, features = ["embassy", "defmt"] }For desktop use with log (instead of defmt):
bletio-host = { git = "https://github.com/themactep/bletio", features = ["log"] }defmt and log are mutually exclusive; log is the default when neither is specified.
use bletio_hci::{HciDriver, HciDriverError};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
struct UartHciDriver {
serial: /* your serial port type */,
}
impl HciDriver for UartHciDriver {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, HciDriverError> {
self.serial.read(buf).await.map_err(|_| HciDriverError::ReadFailure)
}
async fn write(&mut self, buf: &[u8]) -> Result<usize, HciDriverError> {
self.serial.write(buf).await.map_err(|_| HciDriverError::WriteFailure)
}
}use bletio_host::{BleHost, BleHostObserver, BleHostStates};
use bletio_hci::HciDriver;
struct MyObserver;
impl BleHostObserver for MyObserver {
async fn ready<H: HciDriver>(
&self,
host: BleHost<H, bletio_host::BleHostStateStandby>,
) -> BleHostStates<H> {
println!("BLE stack ready. Address: {:?}", host.public_device_address());
// Start advertising...
BleHostStates::Standby(host)
}
}#[tokio::main]
async fn main() -> Result<(), bletio_host::Error> {
let observer = MyObserver;
let mut device = bletio_host::BleDevice::builder(observer)
.with_local_name("My BLE Device")
.build();
let hci_driver = UartHciDriver { serial: /* ... */ };
device.run(hci_driver).await
} ┌───────────┐
│ Initial │
└─────┬─────┘
setup()│
┌─────────┴──────────┐
│ Standby │◄────────────────────────────┐
└──┬─────────┬───────┘ │
│ │ │
start_ │ start_ │ connect() │
adv() │ scan() │ │
┌─────┴┐ ┌──────┴──────┐ ┌────────────┐ │
│Adv. │ │ Scanning │ │ Initiating │ │
└──┬───┘ └──┬──┬───────┘ └─────┬──────┘ │
│ │ │ │ │
│ connect│ │ stop_scan() │ connection_complete │
│ event │ │ │ │
│ ┌────┘ │ └──────────┐ │
│ │ └──────────────────┐ │ │
│ │ │ │ │
┌──┴───┴────┐ ┌──────┴───────┴──────┐ │
│Connected │ │ ConnectedCentral │ │
│Peripheral │ │ │ │
└─────┬─────┘ └───────────┬─────────┘ │
│ │ │
│ disconnect / disconnection │ │
└───────────────┬────────────────┘ │
└───────────────────────────────┘
| Job | Description |
|---|---|
| Rustfmt | Enforces formatting with cargo fmt --all --check |
| Test | Runs cargo test with Rust cache |
| Clippy | Lints with cargo clippy -- -D warnings |
| Coverage | Code coverage via cargo-tarpaulin |
| Security Audit | Daily cargo-deny advisory scan |
Dual-licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
The sections below outline a phased roadmap for bringing bletio from a connection-management
stack to a full-featured BLE host. Each phase builds on the previous one and includes
concrete, actionable items.
These items address the highest-priority issues in the current codebase with minimal risk to the existing architecture.
| # | Item | Priority | Effort | Status | Description |
|---|---|---|---|---|---|
| 1.1 | Fix ACL data todo!() panics |
🔴 Critical | M | ✅ Done | Added Event::AclData(AclData) variant; ACL data is now buffered in send_command_and_wait_response, wait_for_event, and wait_controller_ready instead of panicking. |
| 1.2 | Double-buffer the event list | 🟡 High | S | ✅ Done | Bumped default event capacity from 4 to 8. EVENT_LIST_NB_EVENTS const in event/mod.rs for easy tuning. |
| 1.3 | Add log support as alternative to defmt |
🟡 High | S | ✅ Done | Added log 0.4 as optional dependency with log feature flag on all three crates. All diagnostic call sites now support both defmt and log. |
| 1.4 | Document the public API | 🟡 High | M | ✅ Done | Comprehensive rustdoc on key types: Hci, HciDriver, BleDevice, BleHostObserver, AclData, GattClient, GattServer. Includes examples, callback tables, and links to demo code. |
| 1.5 | Remove UnexpectedEvent fallibility |
🟢 Medium | XS | ✅ Done | Replaced Err(Error::UnexpectedEvent) in execute_command_with_command_status_response with unreachable!(). |
| 1.6 | Add Unsupported event logging |
🟢 Medium | XS | ✅ Done | Unknown HCI event codes are now logged via defmt or log instead of being silently dropped. |
| 1.7 | Improve Packet/Event enum sizes |
🟢 Medium | S | ✅ Done | Documented in size_of.txt. LeAdvertisingReportList at 260 bytes is the dominant variant; cannot reduce without alloc or breaking changes. |
| 1.8 | Controller reset timeout | 🟢 Medium | S | ✅ Done | cmd_reset() uses separate HCI_RESET_TIMEOUT (5 seconds) instead of the default 1-second command timeout. |
The stack currently handles only the control plane (commands and events). The data plane (ACL packets carrying ATT/GATT/SMP traffic) is the next critical layer.
| # | Item | Priority | Effort | Status | Description |
|---|---|---|---|---|---|
| 2.1 | ACL send path | 🔴 Critical | M | ✅ Done | Added write_acl_data() to Hci<H>, send_acl_data() to BleHost connected states, EncodeToBuffer impl and public getters/builders on AclData. ACL data is encoded in HCI ACL packet format and written via the driver. |
| 2.2 | ACL receive path | 🔴 Critical | M | ✅ Done | Event::AclData variant (added in Phase 1.1) is now wired through BleDevice to the observer's acl_data_received callback. Received ACL data is dispatched per-connection-handle to the application. |
| 2.3 | ACL credit-based flow control (LE-Credit) | 🟡 High | L | ✅ Done | le_acl_credits tracking on Hci<H>. write_acl_data() checks/decrements credits, returns ControllerBusy when exhausted. LeFlowControlCreditEvent parsing restores credits. Initialized from cmd_le_read_buffer_size during setup. |
| 2.4 | Connection handle registry | 🟡 High | M | ✅ Done | ConnectionRegistry<const MAX> with add/find/remove/update_params. Connection struct tracks handle, role, interval, latency, timeout, peer address. PeripheralConnectionRegistry (1 conn) and CentralConnectionRegistry (4 conns) aliases. |
| 2.5 | Add AclData as observer callback |
🟢 Medium | M | ✅ Done | Added acl_data_received callback to BleHostObserver (push model). Both Event::AclData variant and observer callback coexist — events are buffered in the HCI layer, then dispatched to the observer. |
With ACL data flowing, the next layer is the Attribute Protocol (ATT) and Generic Attribute Profile (GATT).
| # | Item | Priority | Effort | Status | Description |
|---|---|---|---|---|---|
| 3.1 | ATT PDU encoding/decoding | 🔴 Critical | L | ✅ Done | 16 ATT PDU types implemented with EncodeToBuffer serialization and nom-based zero-copy parsing. Supports Error Response, Exchange MTU, Find Information, Read By Type/Group Type, Read/Read Blob, Write Request/Command, Handle Value Notification/Indication/Confirmation. Supporting types: AttHandle, AttValue, AttUuid, AttError, AttErrorCode, AttOpcode. |
| 3.2 | ATT client state machine | 🔴 Critical | XL | ✅ Done | Synchronous request-response state machine (AttClient) with one-outstanding-request tracking. Methods: prepare_exchange_mtu, prepare_read_by_group_type, prepare_read_by_type, prepare_read, prepare_read_blob, prepare_write_request, prepare_write_command, prepare_notification, prepare_indication. receive() matches incoming ACL data to pending responses. Returns EncodedAttPdu for sending over ACL. |
| 3.3 | GATT discovery procedures | 🔴 Critical | L | ✅ Done | GattClient with discover_all_primary_services(), discover_characteristics(), discover_descriptors(). Handles continuation across multiple Read By Group Type/Type responses. Parses 16-bit and 128-bit UUIDs. Attribute Not Found error automatically terminates discovery. |
| 3.4 | GATT client read/write operations | 🟡 High | M | ✅ Done | read_value(), read_blob(), write_value(), write_value_without_response(), notify(), indicate(), send_confirmation(). GattEvent variants: ValueRead, ValueWritten, Notification, Indication. Notifications/indications delivered automatically while idle. Busy-state guard prevents concurrent requests. |
| 3.5 | GATT server framework | 🟡 High | XL | ✅ Done | GattServer with attribute table, handle allocation, handle_request() for all standard ATT requests (Exchange MTU, Find Information, Read By Group Type/Type, Read/Read Blob, Write Request/Command), permission checking. Builder pattern: GattServiceBuilder → GattCharacteristicBuilder → GattDescriptorBuilder. set_value()/get_value() for dynamic values. Standard descriptor UUIDs. |
| 3.6 | GATT profiles | 🟢 Medium | XL | ✅ Done | profiles module with pre-built service definitions: device_information() (DIS), battery_service() (BAS), generic_access() (GAP), generic_attribute() (GATT), heart_rate_service() (HRS). Standard UUID constants for common services, characteristics, and descriptors. |
| # | Item | Priority | Effort | Description |
|---|---|---|---|---|
| 4.1 | SMP command parsing | 🟡 High | L | ✅ Done |
| 4.2 | LE Legacy Pairing | 🟡 High | XL | ✅ Done |
| 4.3 | LE Secure Connections | 🟢 Medium | XL | |
| 4.4 | Bonding & key storage | 🟢 Medium | L | ✅ Done |
| # | Item | Priority | Effort | Description |
|---|---|---|---|---|
| 5.1 | LE Extended Advertising | 🟢 Medium | XL | |
| 5.2 | LE Coded PHY (Long Range) | 🟢 Medium | L | ✅ Done |
| 5.3 | LE Isochronous Channels | 🟢 Low | XL | |
| 5.4 | LE Periodic Advertising with Responses (PAwR) | 🟢 Low | XL | |
| 5.5 | Connection parameter optimization | 🟢 Medium | M | ✅ Done |
| 5.6 | HCI vendor command extension | 🟢 Medium | S | ✅ Done |
| 5.7 | Connection supervision timeout event | 🟢 Medium | S | ✅ Done |
| 5.8 | Client Characteristic Configuration Descriptor (CCCD) writes | 🟢 Medium | M | ✅ Done |
| 5.9 | Dynamic MTU negotiation | 🟢 Medium | S | ✅ Done |
| 5.10 | Power profiling & low-power modes | 🟢 Low | M | ✅ Done |
| # | Item | Priority | Effort | Description |
|---|---|---|---|---|
| 6.1 | Integration tests with virtual controller | 🟡 High | XL | |
| 6.2 | bletio CLI tool |
🟢 Medium | L | |
| 6.3 | Usage examples | 🟡 High | M | ✅ Done |
| 6.4 | Platform support matrix | 🟢 Medium | S | ✅ Done |
| 6.5 | Conformance testing | 🟢 Low | XL | |
| 6.6 | defmt / log structured event tracing |
🟢 Low | M | ✅ Done |
| 6.7 | Semver-gated releases | 🟢 Medium | S | ⬜ |
| # | Item | Status | Description |
|---|---|---|---|
| O.1 | Keep assigned numbers current | ✅ Done | Scheduled CI job updates assigned numbers weekly. |
| O.2 | Security audit | ⬜ | Daily cargo-deny in place; add cargo-audit for RustSec. |
| O.3 | MSRV policy | ✅ Done | rust-version = "1.75", CI job. |
| O.4 | Fuzz testing | ✅ Done | Fuzz tests for HCI, ATT, and SMP parsers. |
| Phase | Focus | Critical Items | Est. Total Effort |
|---|---|---|---|
| 1 | Hardening | ACL todo!(), event list overflow, docs | ~2–3 weeks |
| 2 | Data plane | ACL send/receive, credit flow control, connection registry | ~3–4 weeks |
| 3 | ATT & GATT | ATT PDU encoding, GATT client/server, profiles | ~2–3 months |
| 4 | Security | SMP, LE Legacy Pairing, LE Secure Connections, bonding | ~2–3 months |
| 5 | Features | Extended advertising, Coded PHY, connection optimization | ~2–4 months |
| 6 | Developer exp. | Integration tests, examples, CLI, platform matrix | ~2–3 months |
Effort estimates assume single-developer velocity and are rough order-of-magnitude guides.