OXRecorder::OXLEDManager
WS2812 status LED — color + pattern per application state
Overview
OXLEDManager drives a single WS2812
(NeoPixel) RGB LED to show what the device is doing. You set a high-level
SystemStatus from anywhere in the
firmware, pump animate() from loop(), and the LED
shows a distinct color + animation for that state — so a glance tells you
whether the device is pairing, recording, streaming, faulted, and so on.
- Drives the LED through the ESP32 core's
neopixelWrite()— no external library. - Default data pin is GPIO4 (the ESP32-S3 DI line); overridable in
begin(). - Patterns: solid, blink (slow/normal/fast), smooth pulse, and a heartbeat double-blink.
- Designed to be shared project-wide: one instance,
setStatus()from any subsystem.
Declared in include/OXLEDManager.h; portable core in
src/OXLEDManager.cpp; the WS2812 backend in
src/led/Ws2812Device.cpp.
Wiring
| WS2812 pin | ESP32-S3 Super Mini | Notes |
|---|---|---|
| DI (data in) | GPIO4 | Default; use a level shifter when the LED VDD is 5 V. |
| VDD / 5V | 5V (or 3V3) | WS2812 prefers 5 V; many single LEDs work at 3.3 V. |
| GND | GND | Common ground with the board. |
System states & their indications
Each SystemStatus maps to one color + pattern. The colors are
chosen to be unambiguous at a glance: blue = Bluetooth,
yellow = Wi-Fi link, green = connected /
operational, purple = recording, cyan =
active data transfer, orange = power/battery,
magenta = firmware update, white =
startup/reset, red = fault.
Startup
| Status | Indication | Meaning |
|---|---|---|
Boot | White solid | Firmware booting / self-test, before any subsystem is up. |
Idle | Green pulse (dim breathing) | Powered and healthy but doing nothing — no recording, no active link. Resting state. |
Bluetooth provisioning
| Status | Indication | Meaning |
|---|---|---|
BluetoothPairing | Blue blink | Advertising / discoverable, waiting for a phone to connect (initial pairing mode). |
BluetoothPaired | Blue solid | A phone is connected; waiting for it to push Wi-Fi / server configuration. |
BluetoothConfiguring | Cyan fast blink | Receiving / applying configuration over Bluetooth. |
Wi-Fi connectivity
| Status | Indication | Meaning |
|---|---|---|
WifiConnecting | Yellow blink | Credentials known; joining the access point. |
WifiConnectedNoServer | Yellow solid | On Wi-Fi, but not connected to the backend server (idle, not recording). |
WifiAvailableNoServer | Yellow slow blink | Wi-Fi associated but the server is unreachable and nothing is recording (degraded). |
ServerConnected | Green solid | Connected to the backend server, idle (not recording). |
Recording
| Status | Indication | Meaning |
|---|---|---|
Recording | Purple solid | Capturing audio with NO Wi-Fi transmission (local-only recording). |
RecordingWifiNoServer | Purple fast blink | Recording locally while Wi-Fi is up but the server is not connected. |
RecordingAndStreaming | Green pulse | Connected to the server AND recording/streaming — the fully-operational state. |
Data transfer
| Status | Indication | Meaning |
|---|---|---|
WifiTransmitting | Cyan pulse | Actively uploading data over Wi-Fi (e.g. flushing recordings to the server). |
Maintenance & power
| Status | Indication | Meaning |
|---|---|---|
OtaUpdate | Magenta pulse | Firmware update in progress — do not power off. |
Charging | Orange pulse | Battery charging. |
LowBattery | Orange fast blink | Battery low; needs charging soon. |
FactoryReset | White fast blink | Erasing configuration / returning to defaults. |
Faults
| Status | Indication | Meaning |
|---|---|---|
SdError | Red double-blink (heartbeat) | SD card missing, unmountable, or write-failing. |
Error | Red fast blink | Generic unrecoverable error / fault. |
The states above refine and extend the original request: the Bluetooth, Wi-Fi, server, and recording modes you listed are all present, split into precise sub-states (e.g. "Wi-Fi available but no server" vs "server connected"), plus added states for boot, idle, OTA update, charging, low battery, factory reset, SD fault, and generic error.
Patterns
| Pattern | Behavior |
|---|---|
Solid | Steady on at the color. |
Blink | Hard on/off, 600 ms period (300 on / 300 off). |
SlowBlink | Like Blink, 1.6 s period — calm / degraded. |
FastBlink | Like Blink, 200 ms period — urgent / active. |
Pulse | Smooth breathing fade in/out, 2 s cycle. |
DoubleBlink | Two quick flashes then a pause (heartbeat), 1.6 s cycle. |
Off | Dark regardless of color. |
API reference
bool begin(int gpioPin = 8, SystemStatus initial = SystemStatus::Boot)
Brings up the LED and sets the initial status. Returns false if no device is available.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
gpioPin | int | 8 | GPIO wired to the WS2812 DI pin. |
initial | SystemStatus | Boot | Status shown immediately after start. |
void setStatus(SystemStatus status)
Sets the current application status; the LED updates on the next animate(). Re-setting the same status is a no-op, so it is cheap to call every loop.
SystemStatus status() const
Returns the current status.
void setStyle(const LedStyle &style)
Shows an explicit color + pattern, bypassing the status map (for a custom indication). The next setStatus() overrides it.
bool animate(uint32_t nowMs)
Advances the animation and updates the LED. Call regularly from loop() with millis(). Returns true if the LED output changed.
| Parameter | Type | Meaning |
|---|---|---|
nowMs | uint32_t | Current time in milliseconds (pass millis()). Drives blink/pulse timing. |
void clear()
Turns the LED off without changing the stored status (e.g. before sleep).
Color currentColor() const
The color currently shown on the LED (after the active pattern's brightness envelope).
static LedStyle styleFor(SystemStatus status)
The color + pattern a status maps to — the single source of truth behind the tables above. Exposed for testing and custom UIs.
Examples
Try it
Flash with pio run -e esp32-s3-super-mini -t upload && pio device monitor.
The status map and animation engine are covered on the host by
pio test -e native (via a fake LED device) — no hardware
needed to check logic changes.
1. Reflect application status
Create one manager, set the status as the app changes state, and pump animate() every loop.
#include <Arduino.h>
#include "OXLEDManager.h"
using namespace OXRecorder;
OXLEDManager statusLed;
void setup() {
statusLed.begin(); // GPIO4, starts in Boot (white solid)
// ... bring up subsystems ...
statusLed.setStatus(SystemStatus::BluetoothPairing); // blue blink
}
void loop() {
statusLed.animate(millis()); // drives the blink/pulse animation
// ... rest of your loop; keep it non-blocking ...
}
2. Drive status from real events
Wherever your connection / recording state changes, call setStatus(). It's a cheap no-op when unchanged.
void onWifiConnected() { statusLed.setStatus(SystemStatus::WifiConnectedNoServer); }
void onServerConnected() { statusLed.setStatus(SystemStatus::ServerConnected); }
void onRecordingStart(bool streaming) {
statusLed.setStatus(streaming ? SystemStatus::RecordingAndStreaming
: SystemStatus::Recording);
}
void onSdFault() { statusLed.setStatus(SystemStatus::SdError); } // red heartbeat
void onLowBattery() { statusLed.setStatus(SystemStatus::LowBattery); }
3. Derive status from flags
A small helper keeps the mapping in one place when several conditions combine:
SystemStatus deriveStatus(bool wifi, bool server, bool recording) {
if (recording && server) return SystemStatus::RecordingAndStreaming;
if (recording && wifi) return SystemStatus::RecordingWifiNoServer;
if (recording) return SystemStatus::Recording;
if (server) return SystemStatus::ServerConnected;
if (wifi) return SystemStatus::WifiConnectedNoServer;
return SystemStatus::Idle;
}
// each loop:
statusLed.setStatus(deriveStatus(wifiUp, serverUp, isRecording));
statusLed.animate(millis());
4. Custom one-off indication
statusLed.setStyle({Colors::Magenta, Pattern::Pulse}); // e.g. OTA
// ... later, return to status-driven behavior:
statusLed.setStatus(SystemStatus::Idle);
5. Override the pin
statusLed.begin(6, SystemStatus::Idle); // WS2812 DI on a custom GPIO6
Notes
- Pump
animate()regularly. Blink and pulse timing come from themillis()you pass; if the loop stalls, animations freeze. Keeploop()non-blocking. - Cheap to over-call.
setStatus()with the current status does nothing, andanimate()only writes the LED when the color actually changes — so calling both every loop is fine. - One LED. This manager drives a single status LED. For an addressable strip, drive multiple pixels behind a custom
ILedDevice.