Open-source crowd lighting, conjured from cheap silicon.
NocturNation is a modular open-source crowd-lighting system: one Director Stick listens to music, detects beats and structural events, and broadcasts light commands to a swarm of Lume Sticks that fire infra-red at PixMob bracelets worn by the audience. A full deployment costs roughly the price of a meal out, and gives smaller bands and art installations the same kind of platform that proprietary stadium fan-lighting systems sell to touring acts at five-figure prices.
This repository is the reference firmware. It runs on the M5StickC Plus2 and the M5StickS3 (the "Sticks"). Both Sticks share a single firmware codebase with hardware-specific abstraction underneath.
Full documentation lives in the nocturnation-docs repository (Notion is the master copy; that repo is the public mirror).
| Document | Audience |
|---|---|
| User manual | Operators setting up a venue. Theory of operation, hardware, firmware install, configuration walk-through, modes and shows, troubleshooting, glossary. |
| QLC+ beginner's guide | Operators driving NocturNation live from a DMX console. From-zero walkthrough: install QLC+, plug a StickC in, programme scenes and chasers. |
| Music orchestrator guide | Operators running a programmed show synchronised to music. Watches the OS now-playing source, walks per-track .cues files, drives the same StickC over USB or Art-Net. |
| FX library | Reference for every FX the orchestrator can fire: parameters, units, defaults. Generated from the FX classes. |
| Protocol manual | Implementers building a third-party transmitter or receiver. Wireless layer, frame formats, class-and-group addressing, PixMob IR annex, conformance, test vectors. |
| Developer guide | Contributors writing new Show plug-ins. The Show base class, analyser hooks, render_fx API, widget composition, persistence, testing. |
| Architecture spec | The full system design that the manuals distil. |
| Item | Notes |
|---|---|
| M5StickC Plus2 or M5StickS3 | The Stick. Either can run as Director, Lume, or both. The S3 is the current first-class reference; the Plus2 (now end-of-life from M5Stack) remains fully supported. See the hardware section of the user manual for the comparison. |
| PixMob Aurora bracelets | The reference target. Distributed at Coldplay's Music of the Spheres tour (2022-2024) and widely available second-hand. Other PixMob product lines are partially compatible but have not been bench-tested. |
| USB-C cable | For flashing the Sticks. |
| A speaker playing music | Anything with a clear kick drum. The reference test track is Vengaboys, We Like to Party (138 BPM). |
| Optional: M5Stack IR Transmitter unit | Plus2 only. Plugged onto the GPIO 26 header pin, it acts as a second IR emitter alongside the built-in LED, roughly doubling coverage. Toggled from Config > IR > External. Not supported on the S3 (header pinout differs and the unit drives a strapping pin → boot mode). |
A useful first deployment is one Director plus one Lume, in a small room with a handful of bracelets. For larger venues see the IR radiation-pattern guidance in the user manual.
git clone https://github.com/ratcliffej/nocturnation-stickc.git
cd nocturnation-stickc
pio run -e m5stack-stickcs3 -t uploadSubstitute m5stack-stickcplus2 for a Plus2. Open the folder in VS Code with the PlatformIO IDE extension for the integrated build/upload flow.
If
piois "command not found", you installed PlatformIO via the VS Code extension rather than as a standalone tool - it bundles its own CLI rather than puttingpioon yourPATH. Either invoke it by full path (~/.platformio/penv/bin/pio run …on macOS) or use the extension's build/upload buttons instead of the terminal.
First flash of a fresh Stick may need the lower side button held down while you plug in the USB cable, to enter the ROM bootloader; later flashes reset into the bootloader automatically.
You can confirm a single freshly-flashed Stick is firing IR - no second device or bracelet required. In Director mode a Stick loops its own light commands back through its IR transmitter, so it lights nearby bracelets and gives you something to verify.
- Power on. After the boot splash the Stick lands in Director mode by default.
- Play music with a clear kick near the Stick's microphone. The reference test track is Vengaboys, We Like to Party (138 BPM). On each detected beat the Stick's screen pulses - that is the render path firing.
- Confirm the IR. Infra-red is invisible to the eye, so point a phone camera at the Stick: on each beat the IR LED shows up as a faint purple-white flicker on screen. Most front (selfie) cameras see IR well; many rear cameras filter it out, so switch to the selfie camera if you see nothing. (Same trick as checking a TV remote.)
- Optional - light a bracelet. A PixMob Aurora held in front of the Stick wakes and lights on each beat.
Buttons: Button 1 (the lower button) cycles between the Simple Beat and Dynamic shows; long-press Button 2 (upper) opens the show picker. For the full button/mode reference, adding a second Lume, and venue setup, read the user manual quickstart.
The firmware has a six-layer plug-in architecture:
- HAL - hardware abstraction (mic, IR, display, buttons, BLE, ESP-NOW). Plus2 and S3 backends.
- DAL - device-abstraction layer. Holds the audio analyser core (BeatDetector, DropDetector, music descriptors), event bus, and the canonical
render_fxdispatch. - Plug-ins -
Pluginbase class with property bags and per-plug-in NVS namespaces. - Analyser - sits on the DAL's mic pipeline; produces beat, drop, and music-descriptor events that the Director's Show consumes.
- Shows - operator-selectable performances. Currently
SimpleBeatShow(a faithful beat-pulse) andDynamicShow(FFT-driven HSV with per-drum group routing). - OutputBindings - Lume-side render targets. Currently
LocalDisplayBinding(LCD pulse) andPixMobIrBinding(infra-red wire encoder, a pure relay).
Every render call flows through render_fx("<class>:<group>", ev) with structured class+group targets. The Director's dispatch fans every call out to ESP-NOW broadcast, the Director's own infra-red transmitter, and the Director's screen pulse - so the Director is treated as one of its own Lumes for output purposes. This loopback is dispatch-side behaviour and is described in detail in the user manual's theory of operation and the protocol manual's class-and-group addressing.
The architecture has settled enough that protocol-level documentation is now public-facing rather than internal design notes - hence the protocol manual. Third-party implementations are welcome.
Three layers.
Native unit tests run on the host - no hardware needed. The suite covers the analyser, the transport, the plug-in surfaces, the show framework, and bit-for-bit IR encoder parity against jamesw343/PixMob_IR's Python reference. Each native environment is declared in platformio.ini (prefixed native); run them with:
pio test -e native # and the other native_* environments listed in platformio.iniBuild verification ensures both firmware environments compile clean:
pio run -e m5stack-stickcs3 -e m5stack-stickcplus2Warnings are treated as signal; the current source compiles clean.
Hardware verification is the only way to verify the audio pipeline, the IR-side rendering, and bracelet response. The recommended ritual is in the user manual.
include/ HAL/DAL/plug-in/transport public interfaces.
src/hal/ HAL backends (m5stickc-plus2, m5stickc-s3).
src/dal/ DAL implementation, analyser, render dispatch.
src/transport/espnow/ ESP-NOW frame encode/decode.
src/modes/ Runtime mode finite-state-machine.
src/shows/ Show plug-ins (simple_beat, dynamic).
src/output_bindings/ Lume-side render targets (local_display, pixmob_ir).
src/visualisations/ Legacy visualisations (kept for migration).
include/pixmob_protocol.h PixMob IR encoder (header-only port from jamesw343/PixMob_IR).
test/ Native unit tests, one folder per native env.
boards/ PlatformIO board definitions.
platformio.ini Build, library, and test configuration.
Documentation (manuals, architecture spec, developer guide) lives in the separate nocturnation-docs repository.
NocturNation runs today as a full Director/Lume system on the M5 Sticks: beat- and section-reactive shows, multi-Lume ESP-NOW fan-out with redundant transmission and signal-quality feedback, and class+group addressing so an operator can light a chosen subset of the audience. A companion receiver and manual-Director app for the EMF Tildagon badge lives in nocturnation-tildagon.
Next:
- Public launch (EMF 2026) - open-source repositories, a landing page, and the Tildagon app submitted to the EMF badge app store.
- DMX / QLC+ integration - a
DmxOutputBindingso NocturNation can drive conventional stage fixtures alongside bracelets.
pixmob::buildCycleProfilesinterprets itsprofileMaskargument as an 8-bit profile mask, but the upstream Python reference treats the same bits as aprofile_id_lo/profile_id_hirange. The function is unused in the current firmware, so runtime behaviour is unaffected. Reconciliation tracked as a long-standing carry-forward.
This project would not exist without the prior reverse-engineering work of Daniel Weidman and James Wilson - both are credited in REFERENCES.md in Harvard format, alongside the canonical PixMob protocol documentation in jamesw343/PixMob_IR.
- Code: MIT (see LICENSE).
- Documentation: CC BY-SA 4.0 (per the project architecture specification §13).
- Hardware designs (when published): CERN-OHL-S 2.0.
Issues and pull requests welcome. Major changes - particularly to the IR encoder, the beat-detection thresholds, or the protocol surface - should reference the protocol manual to confirm the change is in scope before any code lands.
For protocol changes, please regenerate the parity-test reference vectors against jamesw343's Python encoder rather than against the local C++ output, to preserve the upstream-as-truth invariant.
The Director's performance is implemented by a Show plug-in. To add your own, see the developer guide - it covers the Show base class API, the analyser hook surface, class+group routing via render_fx, screen rendering, widget composition, NVS persistence, and the testing pattern, with DynamicShow as the worked example.