A bare-minimum ESP32-C3 (ESP-IDF) + NimBLE example showing a tiny TLV protocol over BLE.
- Advertises a GATT service with three characteristics:
STATUS_UUID(READ): returns counters as a TLV response frame.CONTROL_UUID(READ, WRITE): reads current config; writes accept exactly one TLV.OTA_UUID(READ, WRITE, WRITE_NO_RSP): real OTA flow (BEGIN/CHUNK/COMMIT).
See API.md for the on-wire protocol details (UUIDs, TLV tags, examples). This is intentionally small and designed to be a good starting point for experiments.
Prereqs:
- ESP-IDF tools installed via
esp-idf-sys(this repo usesESP_IDF_VERSION=tag:v5.3.2) - ESP Rust toolchain with the
riscv32imc-esp-espidftarget (typically viaespup) espflashinstalled:cargo install espflashespupinstalled:cargo install espupldproxyinstalled:cargo install ldproxy
For Linux users, follow these additional steps:
- Install Rust if you haven't already:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh- Install the ESP toolchain:
cargo install espup
espup install- Install ESP-IDF dependencies:
# Ubuntu/Debian
sudo apt-get install git clang libc6-dev
# For other distributions, check the ESP-IDF documentation- Install additional tools:
# ESP flashing tool
cargo install espflash
# Linker proxy
cargo install ldproxyCommands:
cargo build
cargo runThe runner is configured in .cargo/config.toml as:
runner = "espflash flash --partition-table partitions-ota.csv --monitor"To specify a particular serial port on Linux:
# Replace /dev/ttyUSB0 with your device's serial port
cargo run --port /dev/ttyUSB0- Standard TLV:
tag:u8, len:u8, value[len] - One TLV per write (keeps write-side error handling simple)
Commands used by this example:
0x01PING (len=0) → increments a counter0x02ECHO (len=N) → last payload is stored and exposed in STATUS0x20SET_CONFIG (len=N) → nested TLVs to update config0x05ACTION (len=1) → dummy action code0x30/0x31/0x32OTA BEGIN/CHUNK/COMMIT (viaOTA_UUID)
Response format (READ STATUS_UUID):
status:u8 { tag:u8, len:u8, value[len] }*