Skip to content

About

Admin dashboard and OTA pipeline for a fleet of ESP32 IoT devices — LED/relay control, RS485 solar meters, browser-based flashing, and MQTT-driven firmware updates.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

LED Control Deploy

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


Table of Contents

  1. Features
  2. Tech Stack
  3. Architecture
  4. Project Structure
  5. Setup Instructions
  6. Environment Variables
  7. API Reference
  8. Device Types
  9. Testing
  10. Security Notes
  11. License

Features

  • Device Fleet Management: Add, edit, and remove ESP32 devices from a single dashboard; device state lives in a devices.json file 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.

Tech Stack

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

Architecture

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):

  1. Dashboard triggers a GitHub Actions workflow_dispatch with the target device and version.
  2. Actions compiles the sketch and uploads the resulting binaries to a firmware server.
  3. Actions publishes the binary URL as a retained MQTT message on the device's OTA topic.
  4. 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.


Project Structure

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

Setup Instructions

Prerequisites

  • 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

Local Development

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 dev

The dashboard will be available at http://localhost:3000/admin/login.html.

Deployment

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.


Environment Variables

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.


API Reference

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

Device Types

  • 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.

Testing

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

Security Notes

  • All admin routes require a session cookie derived from ADMIN_PASSWORD and 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, and SameSite=Strict.
  • Strict security headers (CSP, X-Frame-Options, HSTS, X-Content-Type-Options) are applied at the edge via vercel.json.
  • No credentials are stored in this repository — everything sensitive is supplied through environment variables at deploy time.

License

No license file is currently included; all rights reserved by default unless you add one.


Author

Kasilanka Bhoopesh Siva Srikar

About

Admin dashboard and OTA pipeline for a fleet of ESP32 IoT devices — LED/relay control, RS485 solar meters, browser-based flashing, and MQTT-driven firmware updates.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages