Skip to content

Common problems

Find the symptom you’re seeing in the sidebar on the right, jump to it, and work through the causes in order — they’re listed most-likely first. Almost every problem on this page leaves a trail in show.log or the dashboard console, so when in doubt, start there (see reading show.log).

The current state doesn’t handle the event

Section titled “The current state doesn’t handle the event”

A button is only enabled in states that have an on entry for its event. If nothing responds, the current state simply has no handler for that event — the dashboard even shows the button dark in that state. Add a handler (e.g. "go.tapped": "On") to every state where the button should do something. See the state machine section of the show format reference.

A digital input emits four events: pressed, released, tapped (short press), and held (past holdMs, default 400 ms). A common miss: handling go.pressed when you meant go.tapped, or vice versa. Check which event your state listens for against how you’re actually pressing the button.

Naming an input tapped, pressed, released, or held makes its events read as tapped.tapped — confusing and easy to mis-reference. Validation warns about this. Rename the input to something distinct.

A physical button wired to ground needs "activeLow": true, "pull": "up" on the input. Without them the level is read backwards (or floats), so presses never register — or the input fires constantly. If the input has no pin at all, it’s virtual and only responds from the web dashboard, not physical hardware.

If validation finds any error, the whole show is rejected and the device keeps running the last good show — so “my edits do nothing” usually means “my new show was rejected.” Warnings let the show load anyway.

Every validation run is written to show.log. On save, the dashboard also shows a pass/fail banner and the full report under Console ▸ validation. Errors reject the show; warnings proceed.

  • Referencing before defining. initial must name a real state; every to must target a defined state; start/stop/run must reference a defined script; set/ramp/fade targets must resolve; events must reference defined inputs (or a <script>.done). A segment must come after its parent strip in devices.
  • Pin collisions. Two devices — or a device and an input — on the same GPIO is an error. Give each its own pin.
  • Wrong board field. The board id is used to validate your pins (does the GPIO exist? is it output-capable?). Setting the wrong board gives you warnings, not crashes — but fix it so pin validation actually protects you. Board ids are listed on each board page.

A device set normally lasts one frame. If a script is repainting the same device underneath, your set flashes once and vanishes. Add "sec": <n> to hold it — the value is re-asserted every frame for that long, overriding the script.

A play (or wait) gating things you didn’t expect

Section titled “A play (or wait) gating things you didn’t expect”

Serial children run top to bottom, and a serial play is awaited — the next step won’t start until the sound finishes. If the sound should overlap the next child, mark it "flow": "detached". The same applies to any timed light hit: it gates the sibling after it unless you detach it.

A looping script never completes until stopped, so run-ning it and listening for <script>.done waits forever. Loops are for start/stop; run + .done is for one-shot cutscenes. See the cheatsheet for the flow table.

On boot the device tries your configured WiFi for about 10 seconds; if that times out it falls back to its own access point named <name>_AP. If the dashboard is unreachable, check your computer’s WiFi list for that AP — finding it means the device couldn’t join your network (wrong credentials in config.json, or out of range). Fix the credentials via WiFi setup.

The device advertises itself over mDNS as http://<name>.local/. Some networks and OSes are flaky about mDNS — try the device’s IP address directly instead (both forms work for the dashboard and OTA).

  • Loader-mode devices: the rfx-boot UF2 drive appears when you tap RESET, then press BOOT within half a second — see Recovery for why the order matters. The app’s data drive is separate and appears while the app is running.
  • SD-card boards (ESP32-S3-LCD-1.47): data lives on the removable microSD, not an on-chip drive — prep the card on a PC instead.
  • On-chip-FAT boards (T-Display-S3, S3-CAM, S3-Zero): you can also flash a project’s data/ in one shot with pio run -e <env> -t uploadfs instead of hand-copying files. See your board page for which storage your board uses.

The sound plays once but won’t retrigger while playing

Section titled “The sound plays once but won’t retrigger while playing”

overlap defaults to 1 — monophonic, so relaunching a sound restarts it rather than layering a second copy. For overlapping shots, set "overlap": N on the script (and optionally steal: restart the oldest voice — the default — the newest, or drop the new trigger when all voices are busy).

volume is 0..1, not a percentage. "volume": 50 is just clamped loud.

Speed flows downhill: slowing a script down also slows (and pitches down) any audio inside it. If a sound plays at the wrong pitch, check the speed of its parent script — that’s a feature doing its job, not corruption.

play takes a .wav file from the data folder. Check the filename matches exactly (including the folder path) and the file actually made it onto the device’s data drive.

You are never locked out. If a bad app crash-loops, the loader’s guard trips after 3 failed boots and drops back to the UF2 drive automatically. To force drive mode yourself: tap RESET, then press BOOT — do not hold BOOT through reset (that’s a strapping pin; holding it through reset enters ROM download mode instead). Then drop a fixed firmware.uf2 on the rfx-boot drive.

OTA writes to the idle slot and keeps the previous firmware on the other slot, so a bad image is never a brick — but automatic rollback isn’t wired up yet, so recovery from a crash-looping image is a USB reflash. Follow the flashing guide; a cable flash always works.

Still stuck? Re-check the basics in the standalone quickstart or the loader quickstart, then walk your show against your first show.