A hand-built addressable-LED decoration with ESP32-S3 firmware and a cross-platform Flutter companion app. Built as an engineering thesis project at the Silesian University of Technology.
PixelTree is a physical string of WS2812D ARGB LEDs driven by a Seeed Studio XIAO ESP32-S3 microcontroller. The firmware renders 42 animated lighting effects in real time and exposes a REST API over Wi-Fi; the mobile app discovers devices on the local network, provisions Wi-Fi credentials securely over Bluetooth Low Energy, and controls effects, colors, parameters, and brightness. The hardware lives in a custom 3D-printed enclosure.
| Path | Contents |
|---|---|
Firmware/ |
Arduino C++ firmware for the XIAO ESP32-S3 (the .ino sketch and its headers). |
Mobile/ |
Flutter/Dart companion app for Android and iOS. |
Case/ |
3D-printable enclosure: OpenSCAD source and exported STL files. |
LaTeX/ |
The engineering thesis, its LaTeX sources, schematics, renders, and screenshots. |
The full thesis (Polish) with hardware schematics and design rationale is available at
LaTeX/Projekt_inżynierski_Dominik_Porębski.pdf.
- 42 lighting effects across 9 categories: static, waves, chases, twinkle/sparkle, fire/organic, seasonal, special, breathing/fade, and alarm. Each effect exposes its own live-tunable parameters such as colors, speed, palette, density, and gravity.
- Real-time rendering at a target 60 FPS. Animation runs in a dedicated FreeRTOS task pinned to Core 0, kept independent of the Wi-Fi/BLE stack on Core 1, with a brief crossfade when switching effects.
- Cross-platform app: a single Flutter codebase for Android and iOS, with a layered architecture and English/Polish localization.
- Two connectivity modes: connect the device to an existing home network in Station mode, or have it host its own access point in AP mode when no LAN is available.
- Secure BLE provisioning: Wi-Fi credentials are exchanged over BLE using ECDH (P-256) key agreement, with the Wi-Fi password encrypted via AES-128 before transmission.
- Zero-config discovery: devices advertise over mDNS, so the app finds every PixelTree on the network without manual IP entry. Each device gets a unique name derived from its MAC suffix, such as
PixelTree-A1B2. - Persistent state: selected effect, its parameters, brightness, and Wi-Fi credentials survive reboots via the ESP32 NVS. A factory reset is available by holding the GPIO button for 5 seconds.
graph LR
subgraph Mobile App
APP[Flutter app<br/>Android / iOS]
end
subgraph Transport
BLE[Bluetooth LE<br/>secure provisioning]
HTTP[Wi-Fi / REST + mDNS<br/>control & discovery]
end
subgraph Device
ESP[XIAO ESP32-S3<br/>C++ firmware / FreeRTOS]
LED[WS2812D LED string]
end
APP <-->|ECDH + AES| BLE
APP <-->|JSON over HTTP| HTTP
BLE <--> ESP
HTTP <--> ESP
ESP -->|single-wire data| LED
Typical flow: the app provisions Wi-Fi over BLE, or the user joins the device's AP directly, then the device joins the network and advertises an HTTP service via mDNS, and from then on the app controls lighting through the REST API.
The firmware is organized as a set of single-responsibility modules included from the main sketch:
LEDController: owns the FastLED buffer and the rendering task; dispatches to the effect functions and applies parameter changes live.Effects/EffectDefs/EffectParams/Palettes: the 42 effect implementations, their parameter structs and defaults, and color palettes.WiFiManager: Station/AP mode handling, reconnection with exponential backoff, mDNS, and device naming.BLEProvisioning: BLE GATT server with separate services for Wi-Fi scanning, credential transfer, and ECDH key exchange (mbedTLS).HTTPProvisioning/LEDApi: the async HTTP server and the LED control REST endpoints.NVSManager: persistent storage of credentials, effect, parameters, and brightness.SerialLogger: leveled serial logging.
The app follows a layered structure under Mobile/lib/:
presentation/: onboarding, device list, connection-mode selection, the provisioning wizard, and the control screen.data/:datasources(BLE, HTTP, crypto, local storage),services,repositories, andmodels.core/: theme, colors, BLE UUIDs, and effect metadata.l10n/: English and Polish localizations.
The firmware exposes the following endpoints (JSON, with permissive CORS):
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/api/led/status |
Current power, brightness, and active effect. |
GET |
/api/led/effects |
List of all effects with id, name, and category. |
GET |
/api/led/params |
Parameters of the active effect. |
POST |
/api/led/effect |
Switch effect ({"id": <n>}). |
POST |
/api/led/params |
Update one or more parameters of the active effect. |
POST |
/api/led/power |
Turn the LEDs on/off ({"on": true}). |
POST |
/api/led/brightness |
Set brightness ({"value": 0-255, "save": false}). |
Example:
# Switch to the "Fire" effect (id 18) and dim to ~70%
curl -X POST http://pixeltree-a1b2.local/api/led/effect -H "Content-Type: application/json" -d '{"id":18}'
curl -X POST http://pixeltree-a1b2.local/api/led/brightness -H "Content-Type: application/json" -d '{"value":180,"save":true}'| Component | Notes |
|---|---|
| Seeed Studio XIAO ESP32-S3 | Dual-core Xtensa LX7 @ 240 MHz, Wi-Fi + Bluetooth 5 (LE). Main compute unit. |
| WS2812D LED string | Addressable RGB LEDs on a single-wire data line. The firmware is configured for 75 LEDs. |
| 3D-printed enclosure | Custom FDM-printed case; sources in Case/. |
Key firmware pin/configuration defaults (see Firmware/Config.h):
- LED data on
GPIO44(D7), 75 LEDs, 60 FPS target, 45 W power cap. - Factory-reset button on
GPIO9(active-low, hold 5 s); status LED onGPIO21.
Pin counts and LED count are compile-time constants in
Config.h: adjust them to match your own build.
The firmware is an Arduino sketch. With the Arduino IDE (or arduino-cli):
-
Install the ESP32 board support package (Espressif) and select the XIAO ESP32-S3 board.
-
Install the required libraries via the Library Manager:
- FastLED
- ArduinoJson
- ESPAsyncWebServer and its
AsyncTCPdependency
The BLE stack and mbedTLS crypto come with the ESP32 core.
-
Open
Firmware/Firmware.ino, then compile and flash to the board. -
Open the serial monitor at 921600 baud to watch boot logs.
On first boot (no stored credentials) the device starts in provisioning mode: it raises an access point named PixelTree-XXXX and advertises over BLE for the app to configure.
Requires the Flutter SDK (Dart SDK ^3.10).
cd Mobile
flutter pub get
flutter run # run on a connected device or emulator
flutter test # run the unit/widget testsTo build release artifacts:
flutter build apk # Android
flutter build ios # iOS (requires macOS + Xcode)The provisioning and control flows are captured in LaTeX/source/Screens/: onboarding, BLE/AP setup, network configuration, and the main control screen.
- Firmware: C++ (Arduino), ESP32-S3, FreeRTOS, FastLED, ArduinoJson, ESPAsyncWebServer, mbedTLS.
- Mobile: Flutter / Dart,
flutter_blue_plus(BLE),pointycastle(ECDH/AES),dio(HTTP),bonsoir(mDNS),shared_preferences,flex_color_picker. - Hardware/CAD: WS2812D LEDs, XIAO ESP32-S3, OpenSCAD enclosure.
Dominik Porębski: engineering thesis, "Design and implementation of an ARGB LED decorative lighting system using an ESP32 microcontroller with a dedicated mobile application", Silesian University of Technology, Gliwice, 2026.