ESPectre ESPHome Frontend
September 19, 2026 · View on GitHub
The ESPHome frontend exposes ESPectre as a YAML component with Home Assistant entities. This guide covers provisioning, entity configuration, adoption, and builds. For installation, placement, and the first sensing check, start with SETUP.md.
Getting Started
After flashing, configure Wi-Fi with one of these provisioning paths:
| Method | How |
|---|---|
| USB | Use Improv Serial with ./espectre provision --ssid MyNetwork or any Improv Serial-compatible web flasher, such as the ESPectre web flasher |
| Captive portal | Connect to the ESPectre Fallback network and finish setup in the browser |
The maintained examples enable Improv Serial, return a Device settings URL after provisioning, and keep SSID, password, and BSSID out of YAML.
On ESP32-S2 with the USB CDC logger, the frontend initializes the shared TinyUSB primary console and routes the logger and Improv Serial through it. The frontend owns this USB dependency and console setup; both are outside the SDK package.
For an unstable access-point association, follow TROUBLESHOOTING.md. ESPectre stores a BSSID pin separately from ESPHome's Wi-Fi credentials and applies it without rewriting YAML or rebooting. A failed update reconnects once with the previous pin. The request contract is in API.md, and the capture lifecycle is in CSI.md.
Once Wi-Fi is configured, the device is discovered automatically by Home Assistant through ESPHome.
Official images add the last three MAC bytes to the ESPHome hostname, for example
espectre-a1b2c3.local. This lets one image serve multiple devices on the same
network. The Home Assistant device and entity IDs use the same suffix.
Integration Surface
The frontend maps runtime state and controls into the ESPHome entities listed under Integrated Entities. Those entities are created automatically when the espectre: component is declared.
ESPHome advertises its native API alongside ESPectre's Direct HTTP service. Run ./espectre devices --frontend esphome to find the Direct endpoint. Direct and the ESPHome entities use the same runtime controls; Direct also provides local management and raw CSI collection. See API.md for capabilities and DISCOVERY.md for discovery.
Direct API is enabled by default. To expose only ESPHome's native API and Home Assistant entities, disable it in the component configuration:
espectre:
direct_api: false
This disables Direct HTTP requests, SSE telemetry, raw CSI streaming, _espectre._tcp.local. discovery, and the peer-assisted browser bootstrap responder. It does not disable ESPHome's native API or ESPectre entities.
A successful Direct mutation republishes the affected number or select state, so Home Assistant and Direct clients observe the same runtime configuration. Wi-Fi credentials, OTA, and ESPHome API encryption remain owned by ESPHome. Changing the ESPectre label does not alter the ESPHome hostname, adopted YAML, or entity IDs.
Configuration Surface
Shared sensing options go under espectre: with the names, defaults, and ranges in SDK.md. The YAML schema is in init.py; the Integrated Entities table lists the runtime controls. Use TROUBLESHOOTING.md to decide what to adjust.
Set the capture profile in YAML and rebuild to change it:
espectre:
csi_capture_profile: lltf # auto (default), lltf, or ht-vht
csi_traffic_mode: external
lltf always uses LLTF20. ht-vht selects HT20 or VHT20 from the chip and associated band, including with wifi.band_mode: AUTO on ESP32-C5. The wifi_raw generator requires auto or lltf; incompatible YAML and runtime generator changes are rejected. The profile has no Home Assistant control or Direct mutation. CSI.md owns automatic selection and the LLTF capture behavior.
Diagnostic Telemetry
Press Refresh Diagnostics to publish the latest cached rate sample to Home Assistant. Diagnostic sensors are available in production builds and publish only on request. Direct HTTP also exposes performance, heap, load, and detector timing; see API.md. For interpreting input rates and occupancy, follow TROUBLESHOOTING.md.
Detection Profile Selection
wifi:
band_mode: AUTO # ESP32-C5 only; optional because AUTO is the default
espectre:
detection_algorithm: lightweight # or high_accuracy
Set the Wi-Fi band under wifi:, separately from ESPectre's detector selection. On ESP32-C5, band_mode accepts 2.4GHz, 5GHz, or AUTO and defaults to AUTO. Other supported targets use 2.4 GHz. Capture profiles and the limits of 5 GHz sensing are documented in CSI.md.
The YAML value is the initial profile when no persisted selection exists. The Home Assistant detector_select changes it live and persists the choice across reboot. high_accuracy -> lightweight starts calibration automatically, and calibration_active_sensor reflects automatic and user-triggered calibration state.
See TROUBLESHOOTING.md for profile selection and startup, and ALGORITHMS.md for detector behavior.
Entity Customization
Integrated Entities
| Sensor config | Type | Default name | Description |
|---|---|---|---|
movement_sensor | sensor | Movement Score | Current movement score (0.0–1.0), published every evaluation_interval_ms |
motion_sensor | binary_sensor | Motion Detected | Edge-driven motion state; resets to idle when sensing stops or CSI restarts |
threshold_number | number | Threshold | Runtime probability threshold (0.0–1.0) |
motion_on_hits_number | number | Motion On Hits | Runtime motion-on debounce count (1–20) |
motion_off_hits_number | number | Motion Off Hits | Runtime motion-off debounce count (1–20) |
detector_select | select | Detection Profile | Runtime lightweight / high_accuracy selection |
csi_traffic_mode_select | select | CSI Traffic Ownership | Runtime internal / external selection |
traffic_generator_mode_select | select | CSI Traffic Source | Runtime ping / dns (UDP) / dns_tcp / wifi_raw selection |
sensing_switch | switch | Sensing Enabled | Enables or pauses sensing through the common command engine; publishes the runtime state at startup |
recalibrate_button | button | Recalibrate | Starts runtime recalibration |
calibration_active_sensor | binary_sensor | Calibration Active | Read-only authoritative calibration state |
diagnostics_button | button | Refresh Diagnostics | Publishes the latest cached diagnostic sample on demand |
traffic_rate_sensor | sensor | Traffic TX Rate | Diagnostic traffic rate |
csi_callback_rate_sensor | sensor | CSI Callback Rate | Raw CSI callback rate; diagnostic-only |
csi_accepted_rate_sensor | sensor | CSI Accepted Rate | Raw identity-accepted capture rate before temporal admission; diagnostic-only |
csi_admitted_rate_sensor | sensor | CSI Admitted Rate | Rate admitted to the detector's temporal grid; diagnostic-only |
csi_filtered_rate_sensor | sensor | CSI Filtered Rate | Capture rejection rate; diagnostic-only |
csi_missing_rate_sensor | sensor | CSI Missing Slot Rate | Missing detector slots per second; diagnostic-only |
csi_excess_rate_sensor | sensor | CSI Excess Rate | Non-selected same-slot candidates per second, including candidates replaced by one nearer the slot center; diagnostic-only |
csi_stale_rate_sensor | sensor | CSI Stale Rate | Packets discarded as stale per second; diagnostic-only |
csi_out_of_order_rate_sensor | sensor | CSI Out-of-Order Rate | Duplicate or backward-timestamp packets discarded per second; diagnostic-only |
csi_occupancy_sensor | sensor | CSI Temporal Occupancy | Valid-slot occupancy of the active detector window; diagnostic-only |
wifi_channel_sensor | sensor | WiFi Channel | Current associated Wi-Fi channel; diagnostic-only |
wifi_rssi_sensor | sensor | WiFi RSSI | Current associated Wi-Fi RSSI; diagnostic-only |
All entities support standard ESPHome options such as name, internal, icon, and disabled_by_default. The movement_sensor also supports ESPHome sensor filters:
espectre:
movement_sensor:
name: "Living Room Movement"
internal: true
icon: "mdi:sine-wave"
filters:
- multiply: 100
- clamp:
min_value: 0
max_value: 100
- round: 1
motion_sensor:
name: "Living Room Motion"
icon: "mdi:motion-sensor"
threshold_number:
name: "Living Room Threshold"
Use internal: true on movement_sensor when you want to keep the binary motion entity for automations without publishing the raw score to Home Assistant.
Home Assistant Integration
Once the device is flashed and connected to Wi-Fi:
- Home Assistant discovers it through ESPHome
- Go to Settings -> Devices & Services -> ESPHome
- Configure the discovered device
- The default entities are added automatically
Writable entities report the authoritative runtime state, including when a change is rejected. Direct mutations synchronize the affected entities immediately.
Movement Score updates on the detector evaluation cadence, 250 ms by default. The high-rate path runs while the Movement Score entity exists or a Direct SSE client is connected, so Direct-only configurations do not need an unused Home Assistant sensor. Motion Detected publishes only on filtered state edges.
Threshold also updates after calibration and Lightweight settled-level recovery. Motion-hit controls publish on change, and traffic selects synchronize on connect and after accepted changes.
To reduce Home Assistant recorder history, exclude sensor.*_movement_score from recording. Keep the evaluation cadence set for the detector.
To manage configuration and OTA updates, install ESPHome Device Builder and adopt the discovered device. The adopted configuration uses the GitHub source profile, follows main, and identifies that rolling build as 0.0.0-main. Repository builds launched through ./espectre esphome resolve project_version from the same numeric git describe identity used by the other frontends. First-party CI and release builds override it with the detected build version or release tag.
To install a prebuilt OTA image from GitHub Releases instead, download the espectre-esphome-<channel-or-version>-<chip>-ota.bin asset and upload it over the network:
./espectre esphome flash --chip c6 --device espectre-<mac-suffix>.local --firmware espectre-esphome-3.0.0-esp32c6-ota.bin
An existing installation without a MAC-suffixed hostname remains reachable at
espectre.local for this update. After it restarts with an official image, use
its new espectre-<mac-suffix>.local hostname for subsequent OTA uploads.
To stay on a released version, use the matching prebuilt image rather than the rolling main example.
Dashboard Examples
home-assistant-dashboard.yaml provides motion, movement score, history, controls, and diagnostics.

Home Assistant dashboard with motion state, movement score, movement-versus-threshold history, detection profile, threshold, calibration, and diagnostics.
To import a dashboard:
- Go to Settings -> Dashboards -> Add Dashboard
- Open the dashboard and choose Edit
- Open the raw configuration editor
- Replace the default content with the YAML from the example file
- Save the dashboard
Official images use name_add_mac_suffix: true, so include the six-character MAC suffix in the entity IDs when adapting the dashboard. If you changed the base device name from espectre, update that prefix too. Inspect the exact IDs under the Home Assistant device because an existing registry collision can add a suffix such as _2.
Traffic Configuration
The ESPHome surface exposes the shared runtime traffic settings under espectre:. CSI.md describes traffic modes and pacing; API.md defines ports and external markers; TROUBLESHOOTING.md owns rate and occupancy guidance.
Internal Traffic Generator
espectre:
csi_target_pps: 100
csi_traffic_mode: internal
traffic_generator_mode: ping
traffic_generator_target_ip: "" # Empty uses the gateway; set an IPv4 address to override
The traffic_generator_mode_select entity can change the internal source at runtime, and csi_traffic_mode_select can switch between internal and external ownership. Both selections persist after an accepted change. ESP32-C6 rejects traffic_generator_mode: wifi_raw and omits that option from the select entity; see CSI.md.
External Traffic Mode
To disable the internal generator and rely on external traffic:
espectre:
csi_target_pps: 100
csi_traffic_mode: external
csi_traffic_multicast_group: "239.255.0.1"
For raw collection, keep Direct API enabled and follow CLI.md. The collector selects external traffic mode, which persists after collection.
Build and Consumption
The release, preview, and develop channels publish one full-flash image and one OTA image per supported chip, with lightweight as the initial detector. Both lightweight and high_accuracy are available in the image and can be selected through the persisted runtime detector entity. After adoption, ESPHome Device Builder can compile updates from the device YAML; detection_algorithm sets the initial detector for a fresh configuration rather than limiting which detector the firmware supports.
Checked-in example YAML and ordinary Device Builder builds keep signing disabled. Adoption does not bypass the verifier already on the device. You may instead enable ESPHome's signed_ota_verification with your own signing key. See SETUP.md for USB versus OTA after an official image, and RELEASING.md for key custody.
As an ESPHome external component
Each maintained chip has one canonical example. By default it includes espectre-source-github.yaml, which resolves the component from GitHub:
external_components:
- source:
type: git
url: https://github.com/francescopace/espectre
path: src/cpp/frontend/esphome/components
components: [espectre]
esphome.project.version sets the application version. If omitted, a numeric source ref, such as 3.0.0-rc1, supplies it; other refs and local sources retain ESPHome's default. Application versions are limited to 31 bytes. SDK identity is separate; see SDK.md.
Omit ref to use the repository's default branch, or set a valid tag or commit to pin the component. An empty ref: "" is invalid.
Repository development selects espectre-source-local.yaml instead, which resolves the same component from the local checkout:
external_components:
- source:
type: local
path: ../components
components: [espectre]
Repository CLI
See CLI.md for shared CLI syntax, host-side tools, and wrapper behavior.
./espectre esphome build --chip c6 --clean
./espectre esphome flash --chip c6
./espectre esphome config --chip c6
./espectre esphome monitor --chip c6 --device /dev/cu.usbmodemXXXX
On Windows, use .\espectre.cmd esphome ... from the repository root and pass a COM port such as COM5 to --device when serial access is needed.
The repository CLI keeps the selected canonical YAML and loads the ESPectre component from the local checkout. Use flash for upload-only and monitor for logs.
Hardware and Packaging Notes
Build Toolchain
The ESPHome examples use the native ESP-IDF backend from the ESPHome version pinned in requirements.txt. __init__.py registers this directory with ESP-IDF's component manager. Its CMakeLists.txt compiles the selected SDK through the canonical build definition at CMakeLists.txt. During configuration, CMake checks the fingerprint of the generated Python schema against that SDK. No toolchain override or separate library package is required.
Automatic SDK Configuration
The component sets the required CSI, Wi-Fi power, aggregation, buffer, and lwIP options in init.py. It routes runtime logs through the ESPHome logger and selects TinyUSB CDC for logging and Improv Serial on maintained USB-OTG configurations that need it. Board-specific overrides can go under esp32.framework.sdkconfig_options.
The examples use Improv Serial to avoid the flash, memory, and radio contention costs of a BLE provisioning stack. Keep that choice when adapting an example unless your product requires BLE.
Flash Size and Partitions
The ESPHome frontend fits in 4 MB flash with OTA and uses the board and framework default partition table. A custom project can override it with esp32.partitions; ESPectre itself does not require a custom table.
ESPHome-Specific Troubleshooting
Use TROUBLESHOOTING.md for sensing and connectivity problems, and SETUP.md when the board does not enter download mode.
Bluetooth proxy and CSI occupancy
If enabling BLE reduces CSI occupancy, first compare with Bluetooth disabled. TROUBLESHOOTING.md records the scan comparisons, measured results, and interpretation. For an advertisement-only proxy, the following experimental settings had the lowest CSI impact in those ESP32-S3 tests. Merge them into the existing device configuration, preserving board, Wi-Fi, API, and other required settings:
esp32:
cpu_frequency: 240MHz
framework:
type: esp-idf
sdkconfig_options:
CONFIG_ESP_COEX_SW_COEXIST_ENABLE: n
bluetooth_proxy:
active: false
esp32_ble_tracker:
software_coexistence: false
scan_parameters:
interval: 100ms
window: 5ms
duration: 5min
active: false
continuous: true
espectre:
csi_traffic_mode: internal
traffic_generator_mode: wifi_raw
In the tested ESPHome 2026.8.2 / ESP-IDF 5.5.5 combination, esp32_ble_tracker.software_coexistence: false did not force the ESP-IDF flag off: its SDK default remained enabled even after a clean build. The explicit SDK override above is required to reproduce the tested configuration. Run Clean Build Files, rebuild, and install the firmware. Verify that the generated SDK configuration contains # CONFIG_ESP_COEX_SW_COEXIST_ENABLE is not set.
bluetooth_proxy.active: false disables active GATT proxy connections; it still forwards advertisements. The separate scan_parameters.active: false selects passive scanning. Active GATT connections were not validated with this workaround. Shorter scan windows reduce advertisement reception, so check the BLE devices you rely on alongside CSI occupancy. Keep the detector settings fixed during that comparison; the YAML includes those used in the experiment. For wifi_raw hardware and capture-profile limits, see CSI.md.
View logs
Home Assistant entity updates do not replace the serial status log. The shared runtime forwards its 1 Hz IDLE | csi: / MOTION | csi: heartbeats through the ESPHome sink, so they should appear on USB serial and in esphome logs when the espectre.runtime tag permits INFO messages.
esphome logs <your-config>.yaml
esphome logs <your-config>.yaml --device espectre.local
./espectre monitor --port /dev/cu.usbmodem*
Implementation Map
The frontend uses public SDK headers. See CLI.md for builds against an extracted SDK bundle and ARCHITECTURE.md for source groups and ownership.
ESPHome imports constants from the checked-in sensing_schema.py, generated from the public SDK schema. Importing the component does not read C++ headers. During configuration, CMake compares the schema fingerprint with the selected SDK and rejects mismatches before compilation.
This map is for component maintainers; it is not required for normal installation or tuning.
__init__.py: YAML schema, validation, codegen, native ESP-IDF component registration, and ESPHome build flagsCMakeLists.txt: native ESP-IDF bridge to the canonical shared SDK build definitionespectre.cpp,espectre.h: ESPHome adapter over the shared runtime frontend controllersensor_publisher.cpp: movement and motion publishingthreshold_number.cpp: runtime threshold controlmotion_hits_number.cpp: runtime motion-hit debounce controldetector_select.cpp: persisted runtime detector selectionsensing_switch.cpp: sensing lifecycle controlrecalibrate_button.cpp: runtime recalibration actiontraffic_mode_select.cpp: runtime CSI traffic ownership and generator controlexamples/: production and local-development configurations for ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C5, and ESP32-C6, plus the Home Assistant dashboard