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.

Declared in include/OXLEDManager.h; portable core in src/OXLEDManager.cpp; the WS2812 backend in src/led/Ws2812Device.cpp.

Wiring

WS2812 pinESP32-S3 Super MiniNotes
DI (data in)GPIO4Default; use a level shifter when the LED VDD is 5 V.
VDD / 5V5V (or 3V3)WS2812 prefers 5 V; many single LEDs work at 3.3 V.
GNDGNDCommon 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

StatusIndicationMeaning
BootWhite solidFirmware booting / self-test, before any subsystem is up.
IdleGreen pulse (dim breathing)Powered and healthy but doing nothing — no recording, no active link. Resting state.

Bluetooth provisioning

StatusIndicationMeaning
BluetoothPairingBlue blinkAdvertising / discoverable, waiting for a phone to connect (initial pairing mode).
BluetoothPairedBlue solidA phone is connected; waiting for it to push Wi-Fi / server configuration.
BluetoothConfiguringCyan fast blinkReceiving / applying configuration over Bluetooth.

Wi-Fi connectivity

StatusIndicationMeaning
WifiConnectingYellow blinkCredentials known; joining the access point.
WifiConnectedNoServerYellow solidOn Wi-Fi, but not connected to the backend server (idle, not recording).
WifiAvailableNoServerYellow slow blinkWi-Fi associated but the server is unreachable and nothing is recording (degraded).
ServerConnectedGreen solidConnected to the backend server, idle (not recording).

Recording

StatusIndicationMeaning
RecordingPurple solidCapturing audio with NO Wi-Fi transmission (local-only recording).
RecordingWifiNoServerPurple fast blinkRecording locally while Wi-Fi is up but the server is not connected.
RecordingAndStreamingGreen pulseConnected to the server AND recording/streaming — the fully-operational state.

Data transfer

StatusIndicationMeaning
WifiTransmittingCyan pulseActively uploading data over Wi-Fi (e.g. flushing recordings to the server).

Maintenance & power

StatusIndicationMeaning
OtaUpdateMagenta pulseFirmware update in progress — do not power off.
ChargingOrange pulseBattery charging.
LowBatteryOrange fast blinkBattery low; needs charging soon.
FactoryResetWhite fast blinkErasing configuration / returning to defaults.

Faults

StatusIndicationMeaning
SdErrorRed double-blink (heartbeat)SD card missing, unmountable, or write-failing.
ErrorRed fast blinkGeneric 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

PatternBehavior
SolidSteady on at the color.
BlinkHard on/off, 600 ms period (300 on / 300 off).
SlowBlinkLike Blink, 1.6 s period — calm / degraded.
FastBlinkLike Blink, 200 ms period — urgent / active.
PulseSmooth breathing fade in/out, 2 s cycle.
DoubleBlinkTwo quick flashes then a pause (heartbeat), 1.6 s cycle.
OffDark 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.

ParameterTypeDefaultMeaning
gpioPinint8GPIO wired to the WS2812 DI pin.
initialSystemStatusBootStatus 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.

ParameterTypeMeaning
nowMsuint32_tCurrent 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