Skip to content

Repository files navigation

ESP-Daemon

ESP32-C3 robot status daemon and wireless emergency stop

License: MITPlatformFlasherrelease
DockerROSmicroROSESP--NOWWLED

ESP-Daemon Robot Status PanelESP-Daemon Settings Overview
Home Page
Battery log, emergency button, power controls, micro-ROS link status.
Settings Page
Identity & security, runtime modules, ROS parameters, GPIO and I/O logic.
E-STOP Physical Button PortraitE-STOP Status Mobile
E-STOP hardware
Physical button unit for field wireless stop.
E-STOP status (mobile)
Switch state, ESP-NOW channel, peers/routes, packet counters, route configuration.

Features

CapabilitySummary
ESP-Daemon (main image)HTTP dashboard and settings, three power control groups (GPIO outputs), ADC voltmeter and battery state, onboard NeoPixel strip, ESP-NOW receiver for whitelisted E-STOP sources, optional micro-ROS over UART.
E-Stop (sender image)Debounced GPIO input, ESP-NOW routes to one or more daemon MACs, optional passive buzzer and WLED preset sync over HTTP.
micro-ROSmicro_ros_agent on serial; node publishes std_msgs telemetry and subscribes to std_msgs/Bool per control group (Typical topics).
ESP-NOW emergency pathDaemon matches sender MAC against emergencySources[]; authorized STOP traffic triggers emergency shutdown of selected groups.
ConfigurationStructured JSON export/import, NVS storage; factory reset wipes NVS including Wi-Fi credentials.

Quick start

PhaseAction
Flash FirmwareSelect ESP-Daemon or E-STOP and flash with the Web Flasher.
Network SetupFirst boot: join device AP and complete Wi-Fi provisioning (captive portal).
Web AccessOpen http://<device-name>.local after provisioning.
Default SettingsRuntime modules (ESP-NOW, micro-ROS, LED task, sensor task) are enabled by default; adjust in Settings as needed.

Flash Firmware

Web Serial (Chrome / Edge 89+): connect USB (e.g. XIAO ESP32-C3), open the ESP-Daemon Flasher, choose product ESP-Daemon or E-Stop, CONNECT & FLASH. If enumeration fails, hold BOOT and retry.

PlatformIO (local upload):

cd esp_firmware
# Flash firmware
pio run -e seeed_xiao_esp32c3_prod -t upload
pio run -e seeed_xiao_esp32c3_estop -t upload
# Flash the filesystem (for web UI/assets)
pio run -e seeed_xiao_esp32c3_prod -t uploadfs
pio run -e seeed_xiao_esp32c3_estop -t uploadfs

Network Setup & Web Access

StateBehavior
UnprovisionedDevice opens AP: Daemon ESP-Daemon_XXXXXX, E-STOP ESP-EStop_XXXXXX. Captive portal (192.168.4.1) or manual browser for Wi-Fi setup.
ProvisionedPrefer mDNS: http://esp-daemon_xxxxxx.local, http://esp-estop_xxxxxx.local. Fallback: DHCP-assigned IP.

E-STOP over ESP-NOW

Overview: The E-STOP sender transmits ESP-NOW frames while the button is pressed. The daemon accepts packets only from MAC addresses listed under E-Stop Emergency Sources and applies emergency shutdown to the selected control groups.

ESP-Daemon Settings E-Stop Emergency Sources and Voltmeter

Prerequisites

RequirementNotes
ImagesOne board flashed E-STOP, one ESP-Daemon.
RuntimeESP-NOW enabled on both (Runtime Modules).
RF / channelSame Wi-Fi environment so channel alignment is possible; offline operation uses manual ESP-NOW Channel.
IdentityE-STOP MAC required for whitelist (see GET /device or UI banner).

Daemon configuration (whitelist)

On the Settings page, card E-Stop Emergency Sources (figure above).

FieldConstraint
ESP-NOW Channel1–13 (default 6).
Source MAC address12 hex digits; UI accepts AA-BB-CC-DD-EE-FF style.
Targets (control groups)At least one of Group 1 / 2 / 3 per entry.

Duplicate MACs are rejected. SAVE SETTINGS persists to NVS. Matching is exact MAC; no payload parsing beyond authorized source.

E-Stop sender configuration

UI: /estop.html. Configure one or more routes:

Route fieldPurpose
ESP-Daemon MAC (target)Destination peer MAC.
E-Stop GPIOSwitch input pin.
Switch active when HIGH / Invert pressed/released logicDebounced logic.

While pressed, firmware sends STOP to configured targets at approximately 80 ms intervals.


Voltmeter

Same Settings page (see figure above, Voltmeter card).

ParameterRole
Voltmeter PinADC GPIO.
Divider R1 / Divider R2Divider network; calibration factor x = (R1+R2)/R2 when R2 > 0.
CalibrationRead-only display from R1/R2 in UI; firmware recomputes from divider values.
Offset (V)Trim after bench measurement.
Sliding Window SizeAveraging depth.
Timer Period (us)Sample timer.
Battery Disconnect Threshold / Battery Low ThresholdbatteryLowThresholdbatteryDisconnectThreshold.

Status output:NORMAL, LOW, or DISCONNECTED (debounced connect/disconnect detection).

Host-side low-battery alerting

The device exposes low-battery data via HTTP and ROS 2; desktop or fleet alerting is implemented on the host. DIT-Scripts can provision an Ubuntu desktop host; integrate with either:

  • GET /readingsbatteryStatus
  • Topic /robot_status/battery_voltage (std_msgs/Float32) when micro-ROS is active

micro-ROS & Docker

ESP-Daemon Settings Runtime Modules and ROS

Device settings (Daemon)

SectionFields
Runtime Modulesmicro-ROS enabled.
ROSNode name, domain ID (0–232), timer (ms), micro-ROS timeout (ms), ping interval (ms).

Transport: UART to micro_ros_agent (see firmware Serial).

Recommended startup (auto bootstrap)

From a fresh clone:

cd /path/to/ESP-Daemon
cp .env.example .env
docker compose up -d --build
docker compose logs -f esp-daemon

On first startup, entrypoint.sh automatically checks whether micro_ros_agent exists.
If missing, it runs micro-ROS_install.sh once to bootstrap the workspace. This first run can take several minutes (dependency install + build).

Manual bootstrap (optional)

Use this when you want to prebuild explicitly or force rebuild:

docker compose run --rm esp-daemon bash -lc 'cd "${WS_DIR:-$HOME/esp_daemon}" && ./micro-ROS_install.sh'

Common options:

./micro-ROS_install.sh --force
./micro-ROS_install.sh --skip-rosdep --skip-agent-ws --skip-agent-build

Runtime operation

docker compose up -d
docker compose logs -f esp-daemon

docker-compose.yaml uses entrypoint.sh to source ROS/workspace setup and start:

ros2 run micro_ros_agent micro_ros_agent serial --dev ${MICRO_ROS_SERIAL_DEV}

By default the container maps one serial device via Docker devices and uses MICRO_ROS_SERIAL_DEV=/dev/ttyACM0. Set this variable if your device path is different:

MICRO_ROS_SERIAL_DEV=/dev/ttyUSB0 docker compose up -d --build

Environment mapping

Key runtime variables come from .env:

  • ROS_DOMAIN_ID must match ESP device ROS settings.
  • MICRO_ROS_SERIAL_DEV must match host USB serial path.
  • RMW_IMPLEMENTATION can be rmw_cyclonedds_cpp or rmw_fastrtps_cpp.
  • TZ is optional and defaults to Etc/UTC.
  • AUTO_BOOTSTRAP_MICRO_ROS=true enables first-run auto bootstrap.
  • MICRO_ROS_INSTALL_ARGS passes extra args to bootstrap script (for example --skip-rosdep).

Typical topics

Defined in esp_firmware/lib/ros_node/ros_node.cpp. All messages use std_msgs.

TopicDirMessage typeField
/esp32_counterPubstd_msgs/msg/Int32data — monotonic counter from timer
/robot_status/battery_voltagePubstd_msgs/msg/Float32data — pack voltage (V)
/robot_status/control_group1_enableSubstd_msgs/msg/Booldata — group 1 power enable
/robot_status/control_group2_enableSubstd_msgs/msg/Booldata — group 2 power enable
/robot_status/control_group3_enableSubstd_msgs/msg/Booldata — group 3 power enable

LED and WLED

Daemon (NeoPixel strip)

SettingNotes
Runtime ModulesLED Task enabled.
LED Pin, LED Count, LED Brightness, LED Override Duration (ms)Hardware strip.

Patterns reflect system state (e.g. battery, emergency). Override duration caps temporary modes.

E-STOP (WLED HTTP API)

SettingNotes
estopWledEnabledMaster switch.
estopWledBaseUrlMust include http:// or https://; trailing slash stripped.
estopWledPressedPreset / estopWledReleasedPresetPreset IDs 1–250.

On press/release edges, firmware calls POST {baseUrl}/json/state with {"on": true, "ps": <preset>} when Wi-Fi is connected. Status is exposed on /estop/status (wledStatus).


Configuration & telemetry

TopicDetails
PersistenceSettings are stored in NVS (Preferences). Export/import uses structured JSON (esp-daemon.settings-export / esp-estop.settings-export schemas).
UI actionsEXPORT / IMPORT — backup and restore. RESTORE DEFAULTS — firmware defaults, keeps NVS layout. FACTORY RESET — erases NVS including Wi-Fi credentials; device reboots.
PIN (Daemon only)When PIN protection is enabled, POST /settings/* bodies must include a valid authPin; use POST /settings/unlock from the UI flow. E-STOP settings endpoints do not use PIN.
Battery telemetryGET /telemetry returns a discharge session sampled at 1 Hz while connected (max ~7200 points, ~2 h). Default UI responses are downsampled with LTTB; use ?full=1 for full export, or ?maxPoints=<n> to cap response points.

Updates

MethodEntry
Web FlasherESP-Daemon Flasher (USB, Web Serial).
OTAhttp://<device-name>.local/update (ElegantOTA).
PlatformIOCommands under Flash Firmware.

HTTP API

JSON request bodies use Content-Type: application/json unless noted. Routes depend on firmware image: Daemon and E-STOP expose different paths (see esp_firmware/lib/web_server/web_server.cpp and esp_firmware/lib/web_server/web_server_routes.cpp).

Route map

ESP-Daemon (main image)

MethodPathPurpose
GET/Web UI (index.html)
GET/healthLiveness (ok)
GET/deviceVersion, MAC, settingsPinRequired
GET/readingsSensors + batteryStatus
GET/telemetryBattery discharge session JSON (?maxPoints=<n>, ?full=1)
POST/powerJSON: controlGroup{1,2,3}Power
POST/emergencyJSON: emergency (bool) — control group 1 only (legacy shortcut)
POST/settings/unlockJSON: pin
POST/settings/readJSON: authPin
POST/settingsFull settings JSON + authPin
POST/settings/exportJSON: authPin
POST/settings/importJSON: authPin, settings
POST/settings/resetJSON: authPin
POST/settings/factory-resetJSON: authPin
POST/esp/rebootJSON: authPin
/updateOTA UI (ElegantOTA)

E-STOP (sender image)

MethodPathPurpose
GET/Web UI (estop.html)
GET/deviceVersion, MAC
GET/estop/statusSwitch, routes, ESP-NOW, WLED status
POST/esp/rebootReboot

curl examples

Environment variables:

ESP="http://esp-daemon_xxxxxx.local"
ESTOP="http://esp-estop_xxxxxx.local"
PIN="1234"# if PIN protection enabled in Settings

ESP-Daemon

curl -fsS "$ESP/health"
curl -fsS "$ESP/device"
curl -fsS "$ESP/readings"
curl -fsS "$ESP/telemetry"
curl -fsS "$ESP/telemetry?maxPoints=120"# UI-style downsampled response
curl -fsS "$ESP/telemetry?full=1"# full session (for export/download)
curl -fsS -X POST "$ESP/power" -H "Content-Type: application/json" \
-d '{"controlGroup1Power":true}'
curl -fsS -X POST "$ESP/power" -H "Content-Type: application/json" \
-d '{"controlGroup1Power":false,"controlGroup2Power":false,"controlGroup3Power":false}'# Legacy: group 1 only
curl -fsS -X POST "$ESP/emergency" -H "Content-Type: application/json" \
-d '{"emergency":true}'
curl -fsS -X POST "$ESP/settings/read" -H "Content-Type: application/json" \
-d "{\"authPin\":\"$PIN\"}"
curl -fsS -X POST "$ESP/settings/export" -H "Content-Type: application/json" \
-d "{\"authPin\":\"$PIN\"}"
curl -fsS -X POST "$ESP/esp/reboot" -H "Content-Type: application/json" \
-d "{\"authPin\":\"$PIN\"}"

E-STOP

curl -fsS "$ESTOP/device"
curl -fsS "$ESTOP/estop/status"
curl -fsS -X POST "$ESTOP/esp/reboot" -H "Content-Type: application/json" -d '{"authPin":""}'

Optional: desktop shortcut (Ubuntu)

Settings → Keyboard → Custom Shortcuts: command example for disabling groups 2 and 3 via non-interactive shell:

bash -lc 'curl -fsS -X POST "http://esp-daemon_xxxxxx.local/power" -H "Content-Type: application/json" -d "{\"controlGroup2Power\":false,\"controlGroup3Power\":false}"'

Repository layout

ESP-Daemon/
├── docs/ # Documentation assets
├── esp_daemon_ws/ # ROS 2 / micro-ROS agent workspace
├── esp_firmware/ # ESP32 firmware (ESP-Daemon / E-STOP)
├── tools/ # Web Flasher and related tooling
├── docker-compose.yaml # ROS 2 container
└── README.md

License

MIT License. See LICENSE.

Releases

Sponsor this project

Packages

Used by

Contributors

Languages