The Problem

Home‑assistant users want reliable motion detection without cameras or wearables. Traditional PIR sensors struggle through walls and generate false positives, while video‑based solutions raise privacy concerns. ESPectre addresses this gap by using Wi‑Fi channel state information (CSI) to infer motion, requiring only an inexpensive ESP32 device and a standard router.

What This Does

The repository ships two runtimes:

  • Python host side – runs on a PC/Raspberry Pi, consumes CSI streams, runs detection algorithms, and forwards events via MQTT. Core files are micro‑espectre/src/main.py, csi_streamer.py, ml_detector.py, mvs_detector.py, and the MQTT helpers in src/mqtt/.
  • ESPHome component (C++) – compiled into an ESP‑Home firmware that reads raw CSI from the ESP32 radio and publishes raw packets to the host. The component lives under components/espectre/ and includes csi_manager.cpp, base_detector.cpp, ml_detector.cpp, and the UDP listener in udp_listener.cpp.

The Python side can select between the classic MVS detector (src/mvs_detector.py) and the newer neural‑net detector (src/ml_detector.py). Both expose a uniform interface defined in src/detector_interface.py.

How It Is Wired

Execution start → host

  1. micro‑espectre/src/main.py is the entry point (if __name__ == "__main__":).
  2. It loads configuration from src/config.py (or config_local.py.example).
  3. A CSIStreamer object from csi_streamer.py opens a UDP socket (configured in examples/espectre‑c3.yaml etc.) and yields raw CSI frames.
  4. Each frame is passed to the detector selected by detector_interface.py. For the ML path, MLDetector.detect() in ml_detector.py loads the TFLite model models/motion_detector_small.tflite and runs inference via the TensorFlow Lite interpreter. For the classic path, MVSDetector.detect() in mvs_detector.py calls BaseDetector.process() (implemented in components/espectre/base_detector.cpp via the ESP‑Home component’s native API).
  5. Detection results are handed to the MQTT handler (src/mqtt/handler.py), which publishes a JSON payload on the topic defined in src/mqtt/commands.py. Home Assistant subscribes and creates binary sensor entities automatically.

Execution start → ESP32

  1. The ESP‑Home YAML files in examples/ (e.g., espectre‑c3.yaml) include the custom component espectre.
  2. During firmware compilation, ESP‑Home pulls the C++ sources from components/espectre/.
  3. csi_manager.cpp registers a Wi‑Fi CSI callback with the ESP‑IDF driver, feeding raw packets to udp_listener.cpp.
  4. udp_listener.cpp forwards each packet over UDP to the host IP/port configured in the YAML. No further processing occurs on the device unless the -ml build flag is used, in which case ml_detector.cpp runs the TFLite model locally and publishes a binary sensor state directly via ESP‑Home’s native MQTT bridge.

Key ownership

  • Network I/O – csi_manager.cpp / udp_listener.cpp (device) and csi_streamer.py (host).
  • Detection logic – ml_detector.py + models/*.tflite (host) or ml_detector.cpp (device); mvs_detector.py + base_detector.cpp (host).
  • Home‑Assistant integration – MQTT publishing in src/mqtt/ (host) and ESP‑Home entity definition in the YAML examples (device).

No circular dependencies are present; the host imports the detector modules, while the device code is entirely self‑contained and only communicates via UDP.

How To Use It

# Clone the repo
git clone https://github.com/moses-y/espectre
cd espectre

# Install Python host dependencies (no lockfile, reproducibility not guaranteed)
python -m venv .venv
source .venv/bin/activate
pip install -r micro-espectre/requirements.txt

# Edit the local config (copy example)
cp micro-espectre/src/config_local.py.example micro-espectre/src/config_local.py
# Adjust MQTT broker address, UDP listen port, and detector choice in that file.

# Start the host daemon
python micro-espectre/src/main.py

To flash an ESP32 device:

# Choose an example matching your board
esphome run examples/espectre-c3.yaml   # requires the `esphome` CLI

The ESPHome build pulls the C++ sources automatically. After flashing, ensure the device and the host are on the same network; the host will receive UDP CSI packets and publish motion events to the MQTT broker configured in config_local.py.

Real‑World Use

A smart‑home installer adds an ESP32‑S3 sensor to the living‑room ceiling, points it at the home router, and runs the host daemon on the same Raspberry Pi that hosts Home Assistant. Motion events appear as a binary sensor binary_sensor.espectre_motion, which can trigger automations (e.g., turning on lights when motion is detected while the TV is off).

automation:
  - alias: "Turn on hallway light on motion"
    trigger:
      - platform: state
        entity_id: binary_sensor.espectre_motion
        to: "on"
    action:
      - service: light.turn_on
        target:
          entity_id: light.hallway

Code Health & Issues

  • Low – Dependencies declared without a lockfile – micro-espectre/requirements.txt (reproducible builds not guaranteed).
  • CI defined in .github/workflows/ci.yml; tests exist under micro-espectre/tests/ and test/, but coverage enforcement is not evident.
  • No explicit license file for the Python side; the top‑level LICENSE covers the project overall.
  • No secret scanning; example YAML files contain no credentials, but real deployments must supply MQTT passwords manually.

The Bottom Line

ESPectre provides a functional, low‑cost motion‑detection pipeline that integrates cleanly with Home Assistant via MQTT. The code base is split cleanly between host‑side Python and device‑side C++, making targeted changes straightforward. The main drawback is the lack of a Python lockfile, which can cause environment drift for new developers. Suitable for engineers comfortable with ESPHome and MQTT who need privacy‑preserving motion sensing.