Skip to content

Makerspace-Ashoka/OpenMagTag

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OpenMagTag — Work State Display

A Wi-Fi connected work status display for the LilyGo T-Display-S3 Touch (ESP32-S3). Shows your current work state (BUSY, FREE, FOCUS, CALL, etc.) on a colour touchscreen with a matching WS2812B LED ring. Configured via a built-in mobile-friendly web UI — no app required.


Features

  • Instant status display — full-screen colour + large text for at-a-glance readability across a room
  • LED ring — 6× WS2812B LEDs match the active mode colour and brightness
  • Touch gestures — single tap cycles modes; double tap fires a configurable HTTP webhook
  • Captive portal setup — first boot opens an AP with a browser-based Wi-Fi config page
  • Persistent storage — modes and settings survive reboots (NVS + LittleFS)
  • REST API — full CRUD for modes; integrates with Home Assistant, IFTTT, or any HTTP client
  • Configurable webhooks — single and double tap can POST to any URL (supports HTTPS + custom headers/JWT tokens)
  • mDNS — reachable as http://workdisplay.local on the local network

Hardware

Component Details
Board LilyGo T-Display-S3 Touch
MCU ESP32-S3 (dual-core 240 MHz, 8 MB OPI PSRAM)
Flash 16 MB
Display ST7789V 1.9" 170×320 IPS, I8080 8-bit parallel
Touch CST816S capacitive, I2C
LEDs 6× WS2812B NeoPixel ring on GPIO 43

Default Modes

Five modes are created on first boot:

Mode Colour LED
BUSY Red #ff3b30 Red, 50%
FREE Green #34c759 Green, 50%
FOCUS Orange #ff9500 Orange, 50%
CALL Blue #007aff Blue, 50%
ASLEEP Dark #1c1c1e Off

All modes are fully customisable via the web UI or REST API.


Prerequisites

  • PlatformIO (Core or IDE extension)
  • LilyGo T-Display-S3 Touch board with USB-C cable

Build & Flash

# Clone the repo
git clone https://github.com/your-username/OpenMagTag.git
cd OpenMagTag

# Compile + flash firmware
pio run --target upload

# Build + flash LittleFS (web UI and default config files)
pio run --target uploadfs

# Open serial monitor (115200 baud)
pio device monitor

When to reflash what:

Changed files Command needed
data/index.html only pio run --target uploadfs
Any .cpp / .h file pio run --target upload
Both firmware and web UI pio run --target upload && pio run --target uploadfs

First-Time Setup

  1. Flash firmware and filesystem as above.
  2. Power on the board. The display shows SETUP MODE and the device creates an open Wi-Fi AP called WorkDisplay.
  3. Connect your phone or laptop to WorkDisplay.
  4. A captive portal opens automatically (or navigate to 192.168.4.1).
  5. Select your home/office Wi-Fi network and enter the password.
  6. The device connects, shows its IP address, and begins displaying the active mode.
  7. Access the web UI at the IP shown on screen, or http://workdisplay.local.

Web UI

The single-page app has three tabs:

Modes tab

  • View all configured modes with their colours
  • Tap a mode card to activate it immediately
  • Use the + button to create a new mode (name, background colour, text colour, font size, LED colour, LED brightness)
  • Long-press / delete button to remove a mode
  • Rename the header label (e.g. change "Work State" to "Studio" or "Lab Status")

Wi-Fi tab

  • Shows current connection status, IP address, and hostname
  • Scan and connect to a different network
  • Reset Wi-Fi credentials (device returns to AP/setup mode)

Button tab

  • Configure single tap and double tap actions
  • Each action can fire an HTTP/HTTPS request with custom method, URL, headers, and body
  • Set the double-tap detection window (250 ms / 400 ms / 600 ms / 800 ms)
  • If single tap has no URL configured, it falls back to cycling through modes

Touch Gestures

Gesture Default behaviour Configured behaviour
Single tap Cycle to next mode Fire HTTP webhook (if URL set)
Double tap Nothing Fire HTTP webhook (if URL set)

REST API

All endpoints are on port 80. POST endpoints with a JSON body use Content-Type: application/json.

Status

GET /api/status

Returns Wi-Fi state, IP, active mode name, and UI label.

{
  "connected": true,
  "mode": "station",
  "ssid": "MyNetwork",
  "ip": "192.168.1.42",
  "hostname": "workdisplay",
  "active": "BUSY",
  "label": "Work State"
}

Modes

GET /api/modes

Returns all modes and the active mode name.

POST /api/modes
{ "name": "DND", "bg": "#8e44ad", "fg": "#ffffff", "size": 2, "ledColor": "#8e44ad", "ledBrightness": 128 }

Creates or updates a mode. Name is stored uppercase.

DELETE /api/modes?name=DND

Deletes a mode by name.

Active Mode

GET /api/active

Returns the active mode details (name, bg, fg).

POST /api/active
{ "name": "FOCUS" }

Sets the active mode and triggers an immediate display + LED update.

Wi-Fi

POST /api/connect
{ "ssid": "MyNetwork", "password": "secret" }
POST /api/wifi/reset

Erases credentials and restarts in AP mode.

GET /api/scan

Returns latest Wi-Fi scan results.

Label

POST /api/label
{ "label": "Studio Status" }

Renames the UI header label (max 24 characters).

Button Config

GET /api/button
POST /api/button
{
  "doubleTapMs": 400,
  "single": {
    "enabled": true,
    "method": "POST",
    "url": "https://home-assistant.local/api/webhook/my-hook",
    "headers": "Authorization: Bearer eyJ...",
    "body": "{\"state\": \"busy\"}"
  },
  "double": {
    "enabled": false,
    "method": "POST",
    "url": "",
    "headers": "",
    "body": ""
  }
}

Home Assistant Integration Example

Set single tap to call a Home Assistant webhook:

Field Value
Method POST
URL https://your-ha-instance/api/webhook/work-status
Headers Authorization: Bearer <your-long-lived-token>
Body {"state": "busy"}

The device uses WiFiClientSecure with certificate verification disabled — suitable for local network HA instances with self-signed certs.


Architecture

Dual-Core Threading

AsyncWebServer callbacks run on core 0 (Wi-Fi/TCP task). All LovyanGFX display calls must run on core 1 (Arduino loop()). Direct display calls from web handlers cause race conditions and screen corruption.

The dispatch pattern used throughout the codebase:

// Core 0 — web handler sets a flag
_modePending = true;

// Core 1 — loop() reads the flag and applies the update
if (_modePending.exchange(false)) {
    ModeEntry m = webSrv.getActiveMode();
    display.showMode(m);
    led.setColor(m.ledColor, m.ledBrightness);
}

Module Overview

File Responsibility
src/config.h All GPIO pin definitions and app constants — single source of truth
src/lgfx_config.h LovyanGFX LGFX class — I8080 parallel bus + CST816S touch configuration
src/display_manager.h/.cpp LovyanGFX wrapper; ModeEntry struct; double-tap state machine
src/led_manager.h/.cpp FastLED wrapper for 6× WS2812B on GPIO 43
src/wifi_manager.h/.cpp STA/AP state machine with DNS captive portal; persists credentials to NVS
src/web_server.h/.cpp AsyncWebServer REST API + LittleFS static file serving
src/main.cpp Wires all modules; owns _modePending and _btnCfgPending atomic flags
data/index.html Single-file SPA (no build step); served from LittleFS

Persistent Storage

NVS namespace "wsd" (via Preferences):

  • ssid / pass — Wi-Fi credentials
  • host — mDNS hostname
  • active — name of the current active mode
  • label — UI header label

LittleFS:

  • /modes.json — JSON array of mode objects; created with defaults on first boot
  • /button.json — Button click config; created on first save via web UI

Dependencies

Library Version Purpose
LovyanGFX ^1.2.0 Display + touch driver
ESPAsyncWebServer ^3.3.23 Async HTTP server
AsyncTCP ^3.2.14 Async TCP for ESPAsyncWebServer
ArduinoJson ^7.2.0 JSON serialisation (v7 API)
FastLED ~3.9.0 WS2812B LED control

Note: FastLED is pinned to ~3.9.0. Version 3.10.x has a broken ESP-IDF 5.x audio initialiser that causes a compile error on this platform.


License

MIT

About

An open soure state display for people to help with communication among people in collaborative spaces. Inspired by plugged/wired in culture.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors