For building from source and updating devices over WiFi with PlatformIO installed.
Do this once per device, with a USB cable connected.
1. Flash firmware
pio run -e esp32c3 -t upload # For ESP32-C3 devices (e.g., lightwitch1)
pio run -e esp32dev -t upload # For ESP32-WROOM-32 devices
pio run -e esp32s3 -t upload # For ESP32-S3 devices2. Flash the web UI filesystem
pio run -e esp32c3 -t uploadfs # For ESP32-C3 devices (e.g., lightwitch1)
pio run -e esp32dev -t uploadfs # For ESP32-WROOM-32 devices
pio run -e esp32s3 -t uploadfs # For ESP32-S3 devicesBoth steps are required on a fresh device. Use the environment matching your device type. After this, all further updates can be done over WiFi.
Use this for routine updates once all devices are on WiFi.
Firmware + filesystem (most common):
pio run -t upload_allUploads firmware to all devices in parallel, waits 15 s for reboots, then uploads the filesystem in parallel. Settings are preserved automatically.
Filesystem only (when only data/ files changed):
pio run -t upload_all_fsReplace lightwitch1 with the target device name configured in the web UI.
Firmware only:
pio run -e lightwitch1_ota -t uploadFilesystem only:
pio run -e lightwitch1_ota -t uploadfsFirmware + filesystem:
pio run -e lightwitch1_ota -t upload && pio run -e lightwitch1_ota -t uploadfsSettings (
config.json) are never affected by firmware updates. Filesystem updates preserve settings automatically via NVS backup.
CI publishes every open PR's esp32dev and esp32s3 builds as a GitHub prerelease (tag pr-<number>), so a device can install and track it directly over the air — no PlatformIO needed. esp32c3 is not built for PRs and stays unsupported.
- In the web UI, Settings → Updates: set
Repositoryand a GitHub PAT, enable "Allow installing PR builds", then click Save — none of these need a reboot to take effect. See README: Firmware updates from GitHub releases for the required token permissions. - In Firmware update, click "Load open PRs", pick the PR from the dropdown, then "Install selected build".
- The device flashes firmware + filesystem, preserves scenes, and reboots. It now tracks that PR — the normal "Check"/"Install update" flow follows new pushes to it instead of official releases.
- To go back to official releases, click "Back to stable" next to the tracking badge — it installs the latest release directly, without loading the PR list.
Run the web interface locally without hardware:
npm install
npm run devOpen http://localhost:8080 in a browser. The mock server (server/index.js) handles all REST endpoints and WebSocket, with scenes stored in memory for the duration of the process. Auth is skipped entirely when no environment variables are configured.
Requires Node.js.
JavaScript (server/, the inline script in data/index.html):
npm install
npm run lintConfig: eslint.config.js. Checked in CI on every PR that touches JS.
C++ firmware (src/): formatted per .clang-format (based on the Google style), checked as a whole file in CI:
clang-format -i path/to/file.cppNote that the existing codebase isn't reformatted to this style yet, so CI on src/** changes may fail on pre-existing code until it is.