A WS2812-based scale-model aircraft lighting controller built on the ESP-01 / ESP-01S, with a full browser-based control panel, OTA firmware updates, and no app required.
This manual covers hardware, wiring, power budgeting, first-time setup, and every feature of the web interface. It assumes you already have the two firmware files (AeroLights_ESP01.ino and web_ui.h) flashed to your module — see Flashing the firmware if not.
Like it? Buy me a coffee! Built it? Link it! I'd love to see what you do with it.
- What you need
- Wiring
- Power supply options
- Flashing the firmware
- First boot & connecting
- Web interface overview
- Lighting functions
- Flash modes
- Custom Strobe Patterns
- Scenes
- Strip map — primary, secondary, tertiary
- Power & behaviour settings
- Network settings
- Import / export
- Tools
- REST API reference
- Known limits
- Troubleshooting
- Caveats & honest disclaimers
| Item | Notes |
|---|---|
| ESP-01 or ESP-01S module | ESP-01S strongly recommended — better flash chip, less prone to boot glitches |
| WS2812 / WS2812B / SK6812 LEDs | Individually addressable "NeoPixel"-type. 3mm or 2mm mini versions for small-scale models |
| 3.3V→5V level shifter (e.g. 74AHCT125) or a series diode | See Wiring — do not skip this |
| 470Ω resistor | In series with the data line |
| 1000µF capacitor | Across the first pixel's power pins |
| USB-to-serial adapter (3.3V) | Only needed for the very first flash; all updates after that are OTA |
| Power source | 1S LiPo + regulator, USB, or a spare BEC channel — see Power supply options |
The firmware as currently built runs in DMA mode (USE_DMA 1), which drives the LED strip through the ESP8266's I2S peripheral rather than bit-banging. This is the recommended mode because it doesn't block interrupts during transmission, which keeps the WiFi connection and web server responsive even with a strip running.
In DMA mode, the data pin is fixed in hardware to GPIO3 (the RX pin). This cannot be changed via software or the web UI — the "Data GPIO" selector in the web interface only appears when DMA is not compiled in, since in DMA mode there's nothing to select.
ESP-01 pin Function
──────────────────────────────────────
GPIO3 (RX) → LED strip DIN (via 470Ω resistor)
GND → LED strip GND (common with power supply GND)
3.3V or VCC → see Power supply options — do NOT power LEDs from this pin
Because GPIO3 is used for LED data, the serial monitor is unavailable in this build (Serial.begin() is compiled out). You won't see debug output over USB; all diagnostics happen through the web UI.
If you ever rebuild the firmware with USE_DMA 0 (bit-bang mode via Adafruit NeoPixel), the data pin becomes configurable through the web UI's Data GPIO selector, defaulting to GPIO2. GPIO2 must stay high at boot, so never add a pull-down resistor there.
Level shifting — do not skip this unless in a scale model where the wiring is short and the LEDs are small
For what it's worth, I've powered the entire ESP8266 + WS2812 string from the ESP-01's interface board 3.3V regulator and it works fine, YMMV.
The ESP8266 outputs 3.3V logic. WS2812 LEDs are rated for 5V logic and often work unreliably or not at all on a long strip without proper level shifting. Two options, in order of reliability:
- 74AHCT125 buffer (best) — a single logic buffer IC, ~$0.50, converts 3.3V to a clean 5V signal.
- Series diode trick (cheaper, works for short runs) — put a 1N4001 diode in series between your 5V rail and the LED strip's power input, dropping it to ~4.3–4.4V. This lowers the LED's effective "high" threshold enough that 3.3V logic reads reliably. Doesn't work as well on long strips or high ambient temperatures.
- 470Ω resistor in series on the data line, right at the ESP-01's pin — protects the first LED's input from voltage spikes.
- 1000µF electrolytic capacitor across the +5V and GND at the first pixel — absorbs the current inrush when the whole strip switches on at once.
The power supply's ground and the ESP-01's ground must be common. If you're powering LEDs from a separate battery/BEC than the ESP-01, tie the two grounds together.
The ESP-01 needs a clean 3.3V supply capable of ~200–300mA peaks (WiFi TX bursts draw more than idle). Common approaches:
| Source | Notes |
|---|---|
| USB-to-serial adapter (bench/dev use) | Most USB-serial adapters have a 3.3V pin — fine for bench testing, not for flight |
| AMS1117-3.3 or similar LDO off a 1S LiPo | Simple, cheap, works well. A 1S LiPo (3.7–4.2V) into a 3.3V LDO regulator is a very common setup for small models |
| 5V BEC + onboard 3.3V regulator | If you already have a 5V BEC in the model (e.g. shared with a servo rail), regulate that down to 3.3V for the ESP-01 |
Do not power the ESP-01 directly from a 1S LiPo without regulation — 4.2V fully charged will damage it. Always use a proper 3.3V regulator (AMS1117-3.3, MCP1700-3302, or similar low-dropout part).
Power the LED strip from its own supply rail — never from the ESP-01's 3.3V regulator. The ESP-01's onboard regulator is only rated for tens of milliamps; a strip of WS2812s can draw hundreds of milliamps to several amps depending on LED count and brightness.
Typical setups:
- 5V BEC output (if your model already has one for servos) — LEDs run happily on 5V.
- 2S LiPo → 5V buck regulator — for larger installations.
- Direct from a 1S LiPo (3.7–4.2V) with the diode-drop trick above — works for smaller LED counts where you're not pushing full brightness on every pixel; slightly dimmer/warmer colour than a proper 5V supply.
Each WS2812 can draw up to ~60mA at full white brightness (20mA per colour channel). A strip of 8 LEDs at full white could theoretically draw ~480mA — in practice, real lighting scenes (dim nav lights, brief strobes) draw far less, but the controller doesn't know your battery's real capacity, so:
Set the Current Cap field (Power & behaviour section) to match what your power source can actually deliver. The firmware estimates real-time current draw every frame and automatically scales overall brightness down if the estimate exceeds your configured cap — this prevents a landing-light-plus-strobe scene from browning out your BEC or draining a small battery too fast. Setting it to 0 disables the cap entirely (not recommended unless you've done the maths yourself).
- Board: "Generic ESP8266 Module" (or "ESP-01S 1M" if your board package offers it)
- Flash size: 1MB (FS:64KB OTA:~470KB) — this specific layout is required for OTA updates to work. The default "1M (no SPIFFS)" layout will compile fine but silently break every OTA update afterward.
- CPU frequency: 80MHz
- Reset method: "dtr" (or "ck" on some older/clone modules)
- Upload speed: 115200 (safe default; some modules handle higher rates)
Only one of these, depending on the USE_DMA setting at the top of the .ino:
- DMA mode (default,
USE_DMA 1): NeoPixelBus by Makuna - Bit-bang mode (
USE_DMA 0): Adafruit NeoPixel
Also required (usually bundled with the ESP8266 board package): ESP8266WiFi, ESP8266WebServer, ESP8266mDNS, ESP8266HTTPUpdateServer, ArduinoOTA, DNSServer, EEPROM.
The ESP-01 needs GPIO0 pulled to GND during power-up to enter flashing mode:
USB-Serial ESP-01
──────────────────────
3.3V → VCC, CH_PD (both tied together)
GND → GND, GPIO0 (both tied to ground for flashing)
TX → RXD (GPIO3)
RX → TXD (GPIO1)
Put the module in flash mode (GPIO0 to GND), reset/power-cycle it, upload, then remove the GPIO0-to-GND connection and power-cycle again for normal boot.
Important: because GPIO3 (RX) is used for LED data in the compiled firmware, you'll need to physically disconnect the LED strip's data line from GPIO3 while flashing over serial, then reconnect it afterward — the two uses conflict on the same pin.
Once the firmware is running, all future updates go over WiFi via Tools → Upload (Arduino IDE) with the correct network port selected, or via the web UI's Upload firmware button (Tools section — see §15), which uses the browser-based /update uploader. No more serial cable needed.
On first power-up (or after a factory reset), the module has no saved WiFi network and starts a self-contained access point:
- SSID:
AeroLights-XXXX-AP(or{your-hostname}-APif you've set a custom hostname) - Password:
flyfast1 - URL:
http://192.168.4.1
Most phones will pop up a "Sign in to network" prompt automatically (captive portal detection) — tap it to open the control panel directly. If it doesn't appear, open a browser and go to 192.168.4.1 manually.
In the Network section, enter your WiFi name and password, then Save & restart. On reboot the controller connects to that network. If you've left AP grace at its default (30 seconds), the module's own AP stays live for those first 30 seconds after boot regardless of whether the home network connects — giving you a guaranteed window to reach it even if something's misconfigured.
Once connected, reach the controller at http://{hostname}.local (mDNS) — the default hostname is AeroLights-XXXX unless you've renamed it.
The firmware has a layered recovery system so you're never permanently locked out (see §13 for full detail): it retries the saved network, and if that keeps failing, brings its own AP back up automatically so you can always reach it locally, even mid-flight-session if your field WiFi router reboots.
The page is organized top-to-bottom as: Scene selection, Strip map (LED wiring assignment), Channels (per-function colour/behaviour), Custom Strobe Patterns, Power & behaviour, Network, and Tools. Everything saves automatically as you make changes — there's also an explicit Save to memory button in Tools if you want to force-commit to EEPROM immediately (useful right before disconnecting power).
Each LED on your strip is assigned one or more functions — a function defines what kind of light it behaves as (colour, timing, brightness). There are 26 function slots total:
| Function | Typical use | Default colour |
|---|---|---|
| Nav port | Left wingtip position light | Red |
| Nav starboard | Right wingtip position light | Green |
| Nav tail | Tail/rear position light | White |
| Strobe tail | Tail anti-collision strobe | White, double-flash |
| Strobe left | Left wingtip strobe | White, double-flash |
| Strobe right | Right wingtip strobe | White, double-flash, slight phase offset |
| Beacon top | Upper fuselage anti-collision | Red, pulsing |
| Beacon bottom | Lower fuselage anti-collision | Red, pulsing, opposite phase to top |
| Landing left / right | Landing/approach lights | Warm white |
| Taxi | Nose/taxi light | Warm white, dimmer |
| Aux | Cabin, logo, or general purpose | Warm amber |
| Formation top / side | Formation-flight lights (visible from above/side) | Dim amber |
| Afterburner | Jet exhaust glow | Orange, Flame mode by default |
| Cockpit glow | Interior instrument wash | Very dim red |
| Gear bay | Wheel-well illumination | White |
Custom 1 through Custom 8 — blank slots with fully user-editable names (up to 8 characters, click the ✎ button on the channel card). Use these for anything not covered above: a specific squadron light, a beacon that doesn't fit the standard categories, whatever your model needs.
Each function has its own card in the Channels section:
- Colour swatch — click to open a colour picker
- Level slider (0–255) — this function's own brightness relative to other functions; multiplies against the global Master brightness
- In [scene name] checkbox — whether this function is active in whichever scene is currently selected
- ↺ — reset this one function's colour/mode/timing back to factory default
- ⤓ — export this channel's definition as a standalone file (see §14)
- ✎ (custom channels only) — rename the function
Every function has a Mode, which determines how its brightness varies over time:
| Mode | Behaviour |
|---|---|
| Steady | Constant brightness — no animation |
| Single / Double / Triple flash | A burst of 1/2/3 quick flashes, then a gap for the remainder of the period. Standard aircraft strobe rhythm. |
| Pulse | Smooth sine-wave fade up and down — used for anti-collision beacons |
| Blink | 50% duty-cycle square wave — simple on/off |
| Flame | Organic flickering, like a candle but faster — designed for afterburner/exhaust glow. Uses a hashed pseudo-random sequence so it never visibly repeats |
| ID Strobe | Seven fixed coded flash patterns (NORM, A–F) matching the exterior-lighting panel found on several USN/USAF aircraft (P-3, E-2, C-2, S-3, F/A-18 and others share this switch design) |
| Custom Strobe | Your own user-programmed flash-group sequence — see §9 |
The Period ms and Flash ms fields change meaning depending on the selected mode — the labels update live as you change the mode, no page reload needed:
| Mode | "Period ms" field means... | "Flash ms" field means... |
|---|---|---|
| Steady / Pulse / Blink | Animation cycle length | (unused) |
| Flash 1/2/3 | Animation cycle length | Width of each flash pulse |
| Flame | (unused — see below) | Minimum brightness floor (0 = full depth, 140 = always glowing) |
| ID Strobe | Becomes a dropdown: pattern NORM/A–F | Width of each flash pulse |
| Custom Strobe | Becomes a dropdown: which of your 4 pattern slots | Width of each flash pulse |
Offset (0–255) shifts where in the cycle a function starts — set two functions to offset 0 and 128 to make them alternate exactly opposite each other (e.g. the two beacon lights, or a wig-wag pair of landing lights).
A fully user-programmable flash-sequence system, separate from the fixed ID Strobe presets. Found in its own section below Channels.
A pattern is an ordered list of groups. Each group has a flash count and a gap that follows it, and the whole sequence loops. For example, a "2-4 Flash" pattern is two groups: 2 flashes → gap → 4 flashes → gap → repeat.
- Pick one of the 4 pattern slots.
- Click + Add group, set the flash count and gap (ms) for that group.
- Repeat for up to 4 groups total.
- Every change saves immediately — no separate "save" step needed for group edits.
- Rename the slot (✎) so it's identifiable when you assign it to a channel.
On any channel's card, set Mode → Custom Strobe. The Period field becomes a Strobe Slot dropdown — pick which of your 4 slots that channel should follow. That channel's own Flash ms field still controls the actual pulse width, so the same pattern shape can drive channels with different flash widths.
| Limit | Value |
|---|---|
| Pattern slots | 4 |
| Groups per pattern | 4 |
| Flashes per group | 1–20 |
| Gap per group | 0–20,000 ms |
The "+ Add group" button shows a live count (e.g. + Add group (2/4)) and disables itself at the cap.
A Load example dropdown on each slot offers 13 pre-named shapes (1/2/3/4/6 Flash, and the various N-M combination patterns like 2-4, 3-2, 4-2, etc.) as a starting point. These use illustrative timing, not measured values — the reference charts they're based on print no time axis at all, only the flash grouping. Edit the numbers after loading to match your own reference if you have exact timing from elsewhere.
A scene is a saved set of "which functions are currently active." Sixteen scene slots total:
| Scene | Typical composition |
|---|---|
| Off | Nothing lit |
| Ground | Nav lights + beacons |
| Taxi | Ground + taxi light + cockpit glow |
| Flight | Ground + all strobes + afterburner + cockpit |
| Night | Flight + landing lights + formation lights + aux |
| Ident | Special override — every LED flashes white, ignores scene mask entirely (see below) |
| Combat / EMCON | Formation lights + cockpit only — no position lights, for tactical/low-visibility ops |
| Approach | Night + gear bay illumination |
Custom 1–Custom 8 — build your own scene from scratch. Rename with ✎, same as custom channels.
Scenes aren't edited directly — you tick the "in [scene name]" checkbox on each function's channel card while that scene is selected as active. There's no separate "scene editor" screen; you build a scene by turning functions on/off while previewing it live.
Every scene button has four icons next to it:
- ✎ (custom only) — rename
- ⊹ — copy this scene's composition into another slot
- ↺ — reset just this scene back to factory default (see note below)
- ⤓ — export this scene as a standalone file
What "reset" actually resets: on a built-in scene, ↺ resets which functions are active — not the functions' own colour/mode/timing (those live on the channel cards and have their own ↺). On a custom scene, ↺ resets both the composition and the name back to "Custom N".
Selecting Ident (or pressing Find my plane in Tools) overrides everything: every LED flashes white for a configurable duration, ignoring whatever scene mask or channel settings are otherwise active. Useful for locating the model in tall grass after a rough landing.
Each physical LED on your strip is assigned to a function via the Strip map section — but uniquely, each LED can carry up to three functions simultaneously, with a priority system:
- Primary — the base function (solid dropdown)
- + Secondary — overrides primary whenever it's active in the current scene and its brightness is currently non-zero (dashed border)
- + Tertiary — overrides both primary and secondary under the same condition (dotted border)
A single LED can be a steady red nav light and a flashing white strobe, without any extra wiring. Set primary = Nav Port, secondary = Strobe Left. In a scene where only Nav is active, you get steady red. In a scene where both are active: for the brief 45ms the strobe fires, its white overrides the red; the other 99% of the time, the strobe's brightness is 0, so it steps aside and the steady red shows through.
Same principle extends to three functions — e.g. Nav (primary) + Formation (secondary) + Strobe (tertiary) on one LED, letting a single wingtip position serve nav, formation-flying, and anti-collision duty depending on which scene is active.
Each row: LED index → primary dropdown → + → secondary dropdown → + → tertiary dropdown → per-LED brightness trim → factory-default hint. Every dropdown change saves immediately. Tap the coloured cell above the row to spotlight just that one LED at half brightness for 30 seconds — useful for physically tracing which LED is which if your wiring order isn't obvious.
A single button below the strip map restores every LED's primary/secondary/tertiary mapping to the factory 8-LED default layout.
| Setting | Purpose |
|---|---|
| Current cap (mA) | See §3 — set to match your actual power supply's capability. 0 disables the cap. |
| Min output | A brightness floor (0–60) applied to any lit LED, preventing it from dropping into the WS2812's unreliable near-zero dead zone during fades. Default 0 (off) — raise it only if you notice specific fades flickering at the bottom. |
| Dim curve | How brightness scales perceptually: Gamma 2.2 (standard, perceptually linear), Square root (brighter at the low end — good for nav lights you want visible even at low master brightness), Smoothstep (soft S-curve, good for gentle pulse edges) |
| Data GPIO | Only visible in bit-bang builds (USE_DMA 0) — selects which pin drives the strip. Hidden in the default DMA build since the pin is hardware-fixed. |
| Boot scene | Which scene the controller starts in on power-up |
| Gamma / self-test / autosave checkboxes | Gamma enables the dim curve above; self-test runs a quick LED sweep on every boot; autosave commits changes to EEPROM automatically ~4 seconds after the last edit (recommended on) |
| Field | Notes |
|---|---|
| WiFi name | Your saved network's SSID |
| ⟳ Scan | Live scan for nearby networks, click a result to auto-fill the SSID field |
| Password | Leave blank when saving other settings (like hostname) to keep the existing password unchanged — the field is never pre-filled for security, so submitting blank does NOT erase your saved password |
| Hostname | 1–16 characters, letters/digits/hyphens only, no leading/trailing hyphen. Also sets the AP name ({hostname}-AP) and mDNS address ({hostname}.local) |
| AP grace | How many seconds the module's own AP stays up after boot, even if the home network connects — gives you a guaranteed access window |
| Save & restart | Applies network changes; the restart is deferred by ~600ms internally so the confirmation reaches your browser before the module actually reboots |
| Clear WiFi | Wipes the saved SSID/password without a full factory reset — next boot goes straight to AP-only mode |
If your saved WiFi drops out (router reboot, model flies out of range, etc.), the firmware handles recovery in layers so you're never stuck waiting for a manual power cycle:
- While still connected via pure WiFi (no AP running), it retries the saved network every 30 seconds.
- If that fails for 3 minutes straight, it brings its own AP back up automatically so you can reach it locally.
- From then on, it opens a brief 8-second retry window every 2 minutes — switching the radio to dual AP+STA mode and attempting to reconnect — before settling back to AP-only if that window doesn't succeed. The moment your network answers, it drops the AP and switches back to pure WiFi automatically.
This applies whether the disconnection happened after a successful boot connection, or if the saved network simply wasn't reachable at boot time in the first place — both cases converge on the same recovery mechanism.
Every level of the configuration — full config, individual scenes, individual channels, individual strobe patterns — can be exported as a small JSON file and re-imported, including onto a different controller.
⤓ Export config (Tools section) opens a checklist: every scene, plus category checkboxes for channel definitions (including custom strobe patterns), strip map, per-LED brightness, and global settings. Untick anything you don't want in the file. WiFi credentials are never included in any export.
⤒ Import config reads the file and — for full configs — shows the same kind of checklist, with a twist: every scene gets a target-slot dropdown, so you can import someone else's "Formation" scene from their slot 3 into your slot 7 without overwriting anything else. Scenes whose content already matches what you have are shown greyed out and unticked by default (but still fully selectable if you want to force-copy them anyway — nothing is ever locked).
Individual-item files (single scene, single channel, single strobe pattern) go through the exact same panel, just with one row instead of many.
| Type | Filename pattern | Contains |
|---|---|---|
| Full config | aerolights-{host}.json |
Everything except WiFi |
| Single scene | aerolights-{scenename}.json |
That scene's function mask |
| Single channel | aerolights-{host}-{channelname}.json |
Colour, mode, timing for one function |
| Single strobe pattern | aerolights-{host}-{patternname}.json |
One pattern slot's group sequence |
| Button | Action |
|---|---|
| Save to memory | Force-commit current settings to EEPROM immediately |
| Self-test | Sweep every LED in sequence, showing its assigned colour |
| Find my plane | Trigger the Ident white-flash override for 30 seconds |
| Upload firmware | Opens the browser-based OTA uploader (/update) |
| ⤓ / ⤒ Export / Import config | See §14 |
| Restart | Reboots the controller |
| Factory reset | Erases all settings including WiFi and returns to first-boot defaults |
Every button in the web UI is a thin wrapper around plain HTTP GET requests — useful if you want to script the controller, or integrate it into a home automation system like HomeKit (via Homebridge's http-switch/http-lightbulb accessories) or Home Assistant.
| Endpoint | Purpose |
|---|---|
GET /api/state |
Full current state as JSON |
GET /api/scene?s=N |
Switch to scene N (0–15) |
GET /api/master?v=N |
Set master brightness (0–255) |
GET /api/master?cap=N |
Set current cap in mA |
GET /api/ch?f=N&r=&g=&b=&l=&md=&p=&on=&ph= |
Update a channel's colour/mode/timing |
GET /api/map?i=N&f=N / map2 / map3 |
Set an LED's primary/secondary/tertiary function |
GET /api/ll?i=N&v=N |
Set a single LED's brightness trim |
GET /api/mask?s=N&m=N |
Set a scene's active-function bitmask directly |
GET /api/strobe?slot=N&g=count,gap,... |
Set a custom strobe pattern's groups |
GET /api/ident?s=N |
Trigger Ident flash for N seconds |
GET /api/solo?i=N |
Spotlight a single LED (-1 to cancel) |
GET /api/export |
Download full config as JSON |
GET /api/save / /api/reboot / /api/factory |
Housekeeping |
Home automation integration example (Homebridge http-switch for a scene toggle):
{
"accessory": "HTTP-SWITCH",
"name": "Flight Mode",
"onUrl": "http://plane.local/api/scene?s=3",
"offUrl": "http://plane.local/api/scene?s=1",
"statusUrl": "http://plane.local/api/state",
"statusPattern": "\"sc\":3"
}| Item | Limit |
|---|---|
| LEDs per strip | 60 |
| Total function slots | 26 (18 built-in + 8 custom) |
| Scene slots | 16 (8 built-in + 8 custom) |
| Custom scene/channel/strobe names | 8 characters |
| Custom strobe pattern slots | 4, each up to 4 groups |
| Flashes per strobe group | 1–20 |
| Gap per strobe group | 0–20,000 ms |
| EEPROM config size | ~932 bytes (of a 1024-byte window) |
AP or WiFi keeps dropping. Confirmed fixed in this build via the layered reconnection logic in §13. If you still see drops, check that your power supply for the ESP-01 is clean and adequate (WiFi TX bursts need headroom) — brownout resets look identical to a WiFi problem from the outside.
LEDs flicker or show wrong colours. Almost always a level-shifting or grounding issue — see §2. Confirm the 470Ω resistor and 1000µF capacitor are in place, and that the LED strip's GND is common with the ESP-01's GND.
OTA upload fails or the module doesn't come back after an update. Check the flash size setting is exactly "1MB (FS:64KB OTA:~470KB)" — any other layout doesn't leave room for an OTA partition.
A channel's mode-specific field (e.g. "Flash ms" vs "Min brightness") shows the wrong label. Should update instantly when you change Mode — if it doesn't, you may be on an older build; re-flash the current firmware.
Custom Strobe pattern doesn't seem to do anything. Check that (a) the pattern slot actually has at least one group defined, and (b) the channel using it has Mode set to Custom Strobe with the correct slot selected in the dropdown that replaces "Period ms".
- ID Strobe pattern timing is taken directly from a printed reference chart's labelled gap times (in seconds), but the intra-cluster flash spacing (the tight groups within a burst) isn't labelled on that chart — it's derived from your Flash ms setting using the same rhythm the other flash modes use, not a measured real-world value.
- Custom Strobe quick-fill examples are illustrative only. Their source charts print no time axis at all — edit the numbers to match your own reference if you have exact timing.
- This is a hobby project, not a certified avionics product. Nothing here should be relied on for real anti-collision lighting on a full-scale aircraft.
- Power estimates (the mA figure shown in the header, and the current-cap enforcement) are a firmware-side approximation based on typical WS2812 current draw per colour channel — not a measured value. Budget your battery with margin.
- Custom strobe/scene/channel names are capped at 8 characters due to EEPROM space constraints — this is a hard limit, not a UI oversight.
Questions, bugs, or ideas for the next feature — check the project's GitHub repository for the latest firmware and to open an issue.