MorayGlow
WiFi-connected RGB LED strip controller built on the XIAO ESP32-S3 with custom PCB design, MOSFET drivers, MQTT/REST/WebSocket control, and Home Assistant auto-discovery.
// Overview
MorayGlow is an ESP32-S3 firmware that turns a 12V 5050 RGB LED strip into a smart home device. Three IRLML6344TR N-channel MOSFETs are driven by 1 kHz LEDC PWM on GPIO1, GPIO2 and GPIO6, giving full 8-bit colour control of each channel. An MP1584 buck converter provides regulated 5V from the same 12V rail that powers the strip, keeping the BOM simple.
The device runs an ESPAsyncWebServer serving a LittleFS-hosted single-page UI, a WebSocket endpoint for real-time state push, and a JSON REST API. It simultaneously connects to an MQTT broker and publishes Home Assistant auto-discovery payloads, making it zero-configuration in a standard HA setup. State changes from any source — REST, WebSocket command, or MQTT — funnel through applyLedState() → broadcastState() → mqttPublishState(), keeping all clients in sync.
On first boot (or after a button-hold factory reset) the device starts an access point and serves a captive portal that scans for nearby networks and saves credentials to EEPROM. The mDNS-based device discovery endpoint allows the devices.html UI page to list all MorayGlow units on the local network automatically.
// Key decisions
Why IRLML6344TR?
Logic-level gate threshold (1.0V typical) means it switches fully on at 3.3V. SOT-23 package keeps the board compact, and it handles the current draw of 5050 LED strips comfortably.
Why remove the copper fill?
The original ground pour was causing solder bridging during hot-plate reflow. Removing it resolved the issue without affecting performance — the trace widths were already sized for the current loads.
Why Schottky diode for back-feed protection?
When the board is powered via the 12V input and simultaneously connected to USB for programming, the buck converter output could back-feed into the USB port. The Schottky diode prevents this with minimal voltage drop.
Why keep state.h header-only with ARDUINO guards?
state.h is shared between the ESP32 firmware and the native PlatformIO test environment. The #ifndef ARDUINO guards let it compile on the host for unit tests without pulling in any Arduino headers.
Why use an async WiFi scan?
WiFi.scanNetworks(true) returns immediately and doesn't block the web server or MQTT loop. Results are cached and served from the next /api/networkdata request, with a fresh scan kicked off immediately after each result is consumed.