# Configuring BLE Telemetry for ESPectre CSI Streaming: A Complete Guide

> Configure BLE telemetry for ESPectre CSI streaming. Stream movement-threshold payloads with this complete guide. Learn how to set up BLE channels and characteristics.

- Repository: [Francesco Pace/espectre](https://github.com/francescopace/espectre)
- Tags: how-to-guide
- Published: 2026-06-10

---

**Enable BLE telemetry in ESPectre by setting `ble_channel_enabled: true`, defining three BLE characteristics (telemetry, sysinfo, control), and binding them to the ESPectre component to stream 8-byte movement-threshold payloads at configurable intervals.**

ESPectre is an ESPHome component that captures Wi-Fi Channel State Information (CSI) frames to detect motion and publish telemetry. While it traditionally integrates with Home Assistant via MQTT, the `francescopace/espectre` repository also supports low-latency Bluetooth Low Energy (BLE) streaming for direct device-to-device communication. Configuring BLE telemetry for ESPectre CSI streaming allows external clients like smartphones or custom controllers to receive real-time movement data without Wi-Fi dependency.

## Prerequisites for BLE Telemetry

The BLE telemetry path requires an ESP32-S3 or ESP32 board with Bluetooth support. The implementation relies on the **esp32_ble_server** library, which is compiled with `USE_ESP32_BLE_SERVER` by default on ESP32-S3 targets. You must define three distinct BLE characteristics to handle notifications, system information, and control commands.

## Step-by-Step Configuration Guide

To enable BLE telemetry, you must declare the BLE server infrastructure in ESPHome YAML and bind the specific characteristics to the ESPectre component using the setter methods defined in [`components/espectre/espectre.h`](https://github.com/francescopace/espectre/blob/main/components/espectre/espectre.h).

### Declare the BLE Server and Characteristics

First, instantiate the `esp32_ble_server` component and declare three characteristics with the required UUIDs. The telemetry characteristic must support **notify**, the sysinfo characteristic **read**, and the control characteristic **write** properties.

```yaml
esp32_ble_server:
  id: espectre_ble_server
  name: "ESPectre S3"

ble_characteristic:
  - id: espectre_ble_telemetry
    uuid: "0000c001-0000-1000-8000-00805f9b34fb"
    properties: [ notify ]

  - id: espectre_ble_sysinfo
    uuid: "0000c002-0000-1000-8000-00805f9b34fb"
    properties: [ read ]

  - id: espectre_ble_control
    uuid: "0000c003-0000-1000-8000-00805f9b34fb"
    properties: [ write ]

```

*Source:* This configuration mirrors the reference implementation in [`examples/espectre-s3.yaml`](https://github.com/francescopace/espectre/blob/main/examples/espectre-s3.yaml) (lines 70–87).

### Wire ESPectre to BLE Characteristics

In the ESPectre component configuration, enable the BLE channel and reference the IDs of the server and characteristics you declared above. The `set_ble_channel_enabled`, `set_ble_telemetry_interval_ms`, and characteristic setter methods (declared in [`espectre.h`](https://github.com/francescopace/espectre/blob/main/espectre.h) lines 10–21) handle the runtime wiring.

```yaml
espectre:
  id: espectre
  ble_channel_enabled: true
  ble_telemetry_interval_ms: 40

  ble_server: espectre_ble_server
  ble_telemetry_char: espectre_ble_telemetry
  ble_sysinfo_char: espectre_ble_sysinfo
  ble_control_char: espectre_ble_control

```

### Adjust Notification Intervals

The `ble_telemetry_interval_ms` parameter controls the throttling interval between BLE notifications. The default behavior targets approximately 25 Hz (40 ms), but you can adjust this value to balance update latency against radio congestion. The component stores this value in `ble_telemetry_interval_ms_` and compares it against `last_ble_telemetry_ms_` before emitting data.

## How BLE Telemetry Works Under the Hood

The ESPectre component registers BLE callbacks during setup and transmits data from the CSI manager’s game-mode callback.

### Setup and Callback Registration

In `ESpectreComponent::setup()` (located in [`components/espectre/espectre.cpp`](https://github.com/francescopace/espectre/blob/main/components/espectre/espectre.cpp) lines 86–100), the code checks `ble_channel_enabled_`. If the server or any characteristic is missing, it logs a warning and disables the channel (lines 88–92). When valid, it registers connect and disconnect callbacks (lines 93–95) and a write handler for the control characteristic (lines 95–97).

The connect/disconnect handlers set the `ble_client_connected_` flag, which acts as a guard clause in the telemetry pipeline.

### Game-Mode Callback and Payload Generation

The CSI manager invokes the lambda registered at `csi_manager_.set_game_mode_callback` (lines 18–34). This callback performs the following steps:

1. **Connection Check**: Exits early if `ble_client_connected_` is false (lines 19–21).
2. **Throttling**: Validates the interval since `last_ble_telemetry_ms_` against `ble_telemetry_interval_ms_` (lines 22–27).
3. **Payload Packing**: Constructs an 8-byte payload containing two little-endian floats (`movement` and `threshold`) using `memcpy` (lines 28–31).
4. **Transmission**: Calls `ble_telemetry_char_->set_value()` followed by `notify()` (line 33).

The payload structure is always **8 bytes** (2 × 4 bytes), representing the current motion value and detection threshold.

### Control Command Processing

Data written to `ble_control_char` is converted to a `std::string` and passed to `handle_ble_control_command_` (lines 66–68). Valid ASCII commands include:

- **`reset`** – Restarts the ESPectre component and re-runs calibration.
- **`gain_lock:on`** – Forces gain-lock mode.
- **`gain_lock:off`** – Disables gain-lock mode.

## Client Implementation Example

You can consume the telemetry stream using Python and the `bleak` library. The following script connects to the ESP32 and unpacks the binary payload:

```python
import asyncio
import struct
from bleak import BleakClient

BLE_ADDR = "XX:XX:XX:XX:XX:XX"
TELEMETRY_UUID = "0000c001-0000-1000-8000-00805f9b34fb"

def notification_handler(_, data):
    if len(data) == 8:
        movement, threshold = struct.unpack('<ff', data)
        print(f"Movement: {movement:.4f}, Threshold: {threshold:.4f}")
    else:
        print(f"Received unexpected payload: {data}")

async def main():
    async with BleakClient(BLE_ADDR) as client:
        await client.start_notify(TELEMETRY_UUID, notification_handler)
        await asyncio.sleep(30)

asyncio.run(main())

```

The script subscribes to notifications and decodes the 8-byte payload into two floating-point values representing the current CSI movement metric and the configured detection threshold.

## Summary

- **Enable** the BLE channel by setting `ble_channel_enabled: true` in the ESPectre component configuration.
- **Declare** three BLE characteristics (telemetry for notify, sysinfo for read, control for write) using the standard 16-bit UUIDs provided in the repository examples.
- **Bind** the BLE server and characteristics to the ESPectre component to activate the telemetry stream.
- **Tune** the update rate with `ble_telemetry_interval_ms` to balance latency and throughput.
- **Parse** the 8-byte binary payload on the client side to extract real-time movement and threshold values.

This configuration provides a low-latency, Wi-Fi-independent telemetry path that operates even when the device is isolated from the network, making it ideal for portable motion-based controllers or on-board diagnostics.

## Frequently Asked Questions

### What is the binary payload format for BLE telemetry?

The payload is exactly **8 bytes** containing two 32-bit IEEE 754 floats in little-endian order. The first float represents the current **movement** value calculated from CSI frames, and the second float represents the detection **threshold**. The component packs these values using `memcpy` in [`components/espectre/espectre.cpp`](https://github.com/francescopace/espectre/blob/main/components/espectre/espectre.cpp) (lines 28–31) and transmits them via the `notify()` method.

### How do I change the BLE update rate?

Set the `ble_telemetry_interval_ms` parameter in your ESPHome YAML. The default is 40 milliseconds (25 Hz). The component enforces this interval in the game-mode callback by comparing the current timestamp against `last_ble_telemetry_ms_` (lines 22–27), ensuring the BLE radio is not overwhelmed by high-frequency CSI events.

### Can I use BLE telemetry without Wi-Fi connectivity?

Yes. The BLE transport operates independently of Wi-Fi. The `ble_client_connected_` flag (managed by connect/disconnect callbacks in [`espectre.cpp`](https://github.com/francescopace/espectre/blob/main/espectre.cpp) lines 93–95) gates all telemetry transmission. As long as a BLE client is paired, you will receive CSI data even if the ESP32 is not connected to a Wi-Fi network or MQTT broker.

### How do I send control commands to the ESP32 via BLE?

Write ASCII strings to the control characteristic (UUID `0000c003-0000-1000-8000-00805f9b34fb`). The component parses these commands in `handle_ble_control_command_` (lines 66–68). Supported commands include `reset` to recalibrate the sensor, and `gain_lock:on` or `gain_lock:off` to toggle the automatic gain control lock.