An admin dashboard for managing a fleet of ESP32-based IoT devices — LED strips, relays, and RS485 solar meters — with browser-based flashing, over-the-air firmware updates, and live MQTT control. Built as a static admin panel backed by Vercel serverless functions, with GitHub Actions handling firmware compilation and Cloudflare R2 handling manual binary uploads.
Live Demo: https://ledcontroldeploy.vercel.app
- Features
- Tech Stack
- Architecture
- Project Structure
- Setup Instructions
- Environment Variables
- API Reference
- Device Types
- Testing
- Security Notes
- License
- Device Fleet Management: Add, edit, and remove ESP32 devices from a single dashboard; device state lives in a
devices.jsonfile in a sister firmware repository, fetched fresh on every request. - Over-the-Air Updates: Trigger a firmware build and push straight to a single device or the whole fleet, with per-run status polling broken down by compile / upload / OTA-trigger step.
- Firmware History and Rollback: Walks the git history of each device's sketch file, reads the actual firmware version string at each commit, and can re-trigger a build from any prior commit.
- Browser-Based Flashing: Flash a device directly from Chrome/Edge over USB using ESP Web Tools and Web Serial — no local toolchain required.
- Manual OTA Uploads: Upload a pre-compiled binary straight to object storage for devices that need a firmware push outside the normal build pipeline.
- Live MQTT Workbench: A standalone control panel (
index.html) for publishing/subscribing to device topics directly over WebSocket MQTT, useful for debugging without going through the admin UI. - Session Management: Idle-timeout warning with a countdown banner and one-click "stay logged in," backed by short-lived signed sessions.
| Layer | Technology |
|---|---|
| Admin UI | Vanilla HTML/CSS/JS (no framework, no build step) |
| API | Vercel serverless functions (Node.js, CommonJS) |
| Auth | Edge middleware + constant-time session comparison, HttpOnly/Secure/SameSite cookies |
| Device communication | MQTT over TLS (device commands, OTA triggers) |
| Firmware source of truth | GitHub Contents API (sister firmware repo) |
| Firmware CI | GitHub Actions (arduino-cli compile + upload) |
| Manual firmware storage | Cloudflare R2 (hand-rolled AWS SigV4, no SDK dependency) |
| Browser flashing | ESP Web Tools + Web Serial |
| E2E testing | Playwright |
Auth: middleware.js guards every /admin/* route at the edge. It compares a session cookie against a SHA-256 hash of the admin password using a constant-time comparison. API routes repeat the same check server-side via lib/auth.js. Login itself is rate-limited per IP with a sliding-window lockout after repeated failures.
Data source of truth: There is no database. devices.json lives in a separate, private firmware repository and is read via the GitHub Contents API on every request — the dashboard is a thin, stateless view over that file plus the GitHub Actions run history.
OTA flow (LED/relay devices):
- Dashboard triggers a GitHub Actions
workflow_dispatchwith the target device and version. - Actions compiles the sketch and uploads the resulting binaries to a firmware server.
- Actions publishes the binary URL as a retained MQTT message on the device's OTA topic.
- The device picks up the retained message on its next reconnect, downloads and flashes itself, then clears the retained message so it doesn't refire.
OTA flow (RS485 meters): RS485 devices don't get per-device firmware — they all run one shared binary and identify themselves via a device ID set through a WiFiManager captive portal on first boot. Updates can be pushed to a single meter's OTA topic or fanned out to the whole group.
Manual OTA path: api/manual-ota.js accepts a binary upload and signs the request to Cloudflare R2 by hand (SigV4), avoiding an AWS SDK dependency for a single write path.
led_control_deploy/
├── admin/ # Admin dashboard pages (static HTML)
│ ├── index.html # Fleet overview
│ ├── device.html # Single-device detail/control
│ ├── add-device.html # New device form
│ ├── editor.html # Sketch editor
│ ├── flash.html # Browser-based USB flashing (ESP Web Tools)
│ ├── manual-ota.html # Manual binary upload
│ ├── login.html / session.js # Auth + idle-session handling
│ └── theme.css
├── api/ # Vercel serverless functions
│ ├── auth.js # Login/logout, rate limiting
│ ├── devices.js / add-device.js / update-device.js / delete-device.js
│ ├── push-ota.js # Triggers a firmware build + OTA
│ ├── run-status.js # Polls GitHub Actions run status
│ ├── device-history.js # Firmware version history / rollback
│ ├── flash-manifest.js # ESP Web Tools manifest generator
│ ├── manual-ota.js # Manual binary upload to R2
│ ├── r2-history.js # Lists previously uploaded binaries
│ ├── firmware-source.js # Sketch source retrieval
│ └── mqtt-config.js # Client-facing MQTT connection config
├── lib/
│ ├── auth.js # Session token logic (Node runtime)
│ └── github.js # GitHub Contents/Actions API client
├── index.html # Standalone MQTT workbench
├── middleware.js # Edge auth guard for /admin/*
├── vercel.json # Routing, security headers, function config
├── firebase.json / database.rules.json / .firebaserc # Optional Firebase realtime bindings
├── tests/e2e.spec.js # Playwright end-to-end tests
└── playwright.config.js
- Node.js v18 or higher
- A Vercel account (for deployment) or the Vercel CLI for local dev
- A GitHub personal access token with read/write access to the firmware repo
- An MQTT broker reachable over TLS
- (Optional) Cloudflare R2 bucket, for manual OTA uploads
git clone https://github.com/kbssrikar7/led_control_deploy.git
cd led_control_deploy
# Install the Vercel CLI if you don't have it
npm install -g vercel
# Pull environment variables from your Vercel project, or create .env.local manually
vercel env pull .env.local
# Run locally
vercel devThe dashboard will be available at http://localhost:3000/admin/login.html.
The project deploys as-is to Vercel — no build step. Push to master (or run vercel --prod) with the environment variables below configured in the Vercel project settings.
| Variable | Purpose |
|---|---|
ADMIN_PASSWORD |
Dashboard login password. The session token is a SHA-256 hash of this value. |
GITHUB_TOKEN |
PAT with read/write access to the firmware repo — used to read/write devices.json and trigger Actions runs. |
MQTT_PASS |
Not consumed server-side here; used by device firmware and GitHub Actions to publish OTA triggers. |
R2_ACCOUNT_ID |
Cloudflare account ID, for manual OTA uploads. |
R2_ACCESS_KEY_ID |
R2 access key. |
R2_SECRET_ACCESS_KEY |
R2 secret key. |
R2_BUCKET_NAME |
Target R2 bucket for manually-uploaded binaries. |
Broker host/port and the firmware/worker URLs are configured through environment variables and client-side config rather than hardcoded in documentation.
| Method | Route | Description |
|---|---|---|
| POST | /api/auth |
Log in, sets admin_session cookie |
| DELETE | /api/auth |
Log out, clears the session cookie |
| GET | /api/devices |
List all dashboard-visible devices |
| POST | /api/add-device |
Add a new device; scaffolds firmware from a template for non-RS485 types |
| PUT | /api/update-device |
Update an existing device's metadata |
| DELETE | /api/delete-device |
Remove a device |
| GET | /api/flash-manifest?id= |
Returns an ESP Web Tools manifest for browser flashing |
| POST | /api/push-ota |
Triggers a firmware build + OTA push via GitHub Actions |
| GET | /api/run-status?runId= |
Polls a GitHub Actions run's status, broken down by step |
| GET | /api/device-history?id= |
Firmware version history for a device, read from git commit content |
| POST | /api/manual-ota |
Uploads a pre-compiled binary directly to R2 |
| GET | /api/r2-history |
Lists binaries previously uploaded to R2 |
| GET | /api/mqtt-config |
Returns client-facing MQTT connection parameters |
onoff/rgb— Standard LED/relay devices. Each gets its own scaffolded sketch and a versioned, individually-compiled binary.rs485— Modbus/RS485 solar meters. All devices of this type share a single generic binary; the device identifies itself via a WiFiManager captive portal on first boot rather than through per-device firmware.
End-to-end tests cover login, device management, OTA push flows, and manual uploads using Playwright with mocked API responses.
npm install --no-save @playwright/test
npx playwright install --with-deps chromium
npx playwright test- All admin routes require a session cookie derived from
ADMIN_PASSWORDand are checked with a constant-time comparison to avoid timing attacks. - Login is rate-limited per IP (sliding window, temporary lockout after repeated failures).
- Session cookies are
HttpOnly,Secure, andSameSite=Strict. - Strict security headers (CSP,
X-Frame-Options, HSTS,X-Content-Type-Options) are applied at the edge viavercel.json. - No credentials are stored in this repository — everything sensitive is supplied through environment variables at deploy time.
No license file is currently included; all rights reserved by default unless you add one.
Kasilanka Bhoopesh Siva Srikar