Skip to content

[BUG] ESP32-C6 not getting UDP broadcast #156

Description

@amosyuen

Description

I setup external traffic mode following instructions at https://github.com/francescopace/espectre/blob/main/SETUP.md#external-traffic-mode. The script works fine with unicast, but setting broadcast does not work. I have espectre setup on a generic ESP32-C6 from amazon https://www.amazon.com/dp/B0GC9P74DP. I confirmed from my phone using UDP Monitor app that it is getting the UDP broadcasts so pretty sure my server and the broadcast script is working correctly, but for some reason the ESP32-C6 does not recognize the broadcast.

Steps to Reproduce

  1. Install espectre on ESP32-C6
  2. Configure external traffic with broadcast IP
  3. Don't see espectre updating

Expected Behavior

Espectre should respond to UDP broadcast and update.

Actual Behavior

Espectre did not update.

Environment

  • ESP32 Model: ESP32-C6
  • ESPectre Version/Commit: v1.0.0, main branch
  • Platform: ESPHome
  • Home Assistant Version (if applicable): 2026.8.0
  • ESPHome Version (if applicable): 2026.7.4

Configuration

ESPHome config:

substitutions:
  name: office-heat-pump
  friendly_name: Office Heat Pump
  ha_remote_temp_entity_id: sensor.office_temperature

esphome:
  name: ${name}
  name_add_mac_suffix: false
  friendly_name: ${friendly_name}

esp32:
  variant: esp32c6
  framework:
    type: esp-idf
  
external_components:
  - source: github://francescopace/espectre
    components: [espectre]

logger:
  level: DEBUG
  
api:
  encryption:
    key: <key>

ota:
  password: !secret ota_password
  platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${friendly_name} Hotspot"
    password: !secret fallback_hotspot_password
    
espectre:
  id: espectre_csi
  detection_algorithm: mvs
  traffic_generator_rate: 0 # Disable internal packet generator
  publish_interval: 100 
  evaluation_interval: 25

Broadcast script

#!/usr/bin/env python3
"""
ESPectre Traffic Generator

Generates UDP traffic to trigger CSI extraction on ESPectre devices.
Works on all platforms: Linux, macOS, Windows, Home Assistant.

Usage:
  python3 espectre_traffic_generator.py start         # Start in background
  python3 espectre_traffic_generator.py stop          # Stop running instance
  python3 espectre_traffic_generator.py status        # Check if running
  python3 espectre_traffic_generator.py run           # Run in foreground (Ctrl+C to stop)

Configuration:
  Edit TARGETS, PORT, RATE below.

Home Assistant integration:
  See SETUP.md for command_line switch configuration.

Author: Francesco Pace <francesco.pace@gmail.com>
Thanks to: https://github.com/phoenixtechnam

License: GPLv3
"""
import socket
import time
import signal
import sys
import os
import subprocess

# ============= CONFIGURATION =============
TARGETS = [
    "10.20.255.255",  # Can't get broadcast to work, net mask is 255.255.0.0
]
PORT = 5555
RATE = 100  # packets per second (recommended: 100)
PID_FILE = "/tmp/espectre_traffic.pid"
# =========================================


def start():
    """Start traffic generator in background (daemon mode)."""
    if os.path.exists(PID_FILE):
        with open(PID_FILE) as f:
            pid = int(f.read().strip())
        try:
            os.kill(pid, 0)
            print(f"Already running (PID {pid})")
            return
        except OSError:
            os.remove(PID_FILE)

    script_path = os.path.abspath(__file__)
    python_exe = sys.executable or "python3"

    with open(os.devnull, "w") as devnull:
        popen_kwargs = {
            "stdin": devnull,
            "stdout": devnull,
            "stderr": devnull,
        }
        if os.name == "posix":
            popen_kwargs["start_new_session"] = True
        else:
            popen_kwargs["creationflags"] = (
                subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
            )

        proc = subprocess.Popen([python_exe, script_path, "run"], **popen_kwargs)

    with open(PID_FILE, "w") as f:
        f.write(str(proc.pid))
    print(f"Started (PID {proc.pid})")


def stop():
    """Stop running traffic generator."""
    if not os.path.exists(PID_FILE):
        print("Not running")
        return

    with open(PID_FILE) as f:
        pid = int(f.read().strip())

    try:
        os.kill(pid, signal.SIGTERM)
        print(f"Stopped (PID {pid})")
    except OSError:
        print("Process not found")

    if os.path.exists(PID_FILE):
        os.remove(PID_FILE)


def status():
    """Check if traffic generator is running."""
    if not os.path.exists(PID_FILE):
        print("Not running")
        return

    with open(PID_FILE) as f:
        pid = int(f.read().strip())

    try:
        os.kill(pid, 0)
        print(f"Running (PID {pid})")
    except OSError:
        print("Not running (stale PID file)")
        os.remove(PID_FILE)


def run():
    """Run traffic generator in foreground (Ctrl+C to stop)."""
    print(f"Sending UDP to {TARGETS}:{PORT} @ {RATE} pps (Ctrl+C to stop)")
    run_loop()


def run_loop():
    """Main packet sending loop."""

    def handle_signal(sig, frame):
        sys.exit(0)

    signal.signal(signal.SIGTERM, handle_signal)
    signal.signal(signal.SIGINT, handle_signal)

    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

    interval = 1.0 / RATE
    next_time = time.perf_counter()

    try:
        while True:
            for ip in TARGETS:
                s.sendto(b".", (ip, PORT))
            next_time += interval
            sleep_time = next_time - time.perf_counter()
            if sleep_time > 0:
                time.sleep(sleep_time)
    finally:
        s.close()
        if os.path.exists(PID_FILE):
            os.remove(PID_FILE)


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)

    cmd = sys.argv[1].lower()
    commands = {"start": start, "stop": stop, "status": status, "run": run}

    if cmd in commands:
        commands[cmd]()
    else:
        print(f"Unknown command: {cmd}")
        print("Use: start, stop, status, or run")
        sys.exit(1)

Logs

No applicable logs I saw

Crash Debug Files (if applicable)

Not applicable

Additional Context

Add any other context about the problem here (screenshots, related issues, etc.).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingplatform: espectreC++ ESPHome component

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions