The Physical Button Project is three independent systems that talk to each other over a local network:
┌──────────────────────────────────────────────────────────────────┐
│ Local Network (WiFi) │
│ │
│ ┌──────────────┐ UDP events ┌─────────────────────────┐ │
│ │ Button Device│ ─────────────► │ button-authenticator │ │
│ │ (ESP32-S3) │ │ (Node.js / Express) │ │
│ │ │ ◄───────────── │ SQLite database │ │
│ └──────────────┘ HTTP (config/ └───────────┬─────────────┘ │
│ modes) │ │
│ ┌──────────────┐ │ REST API │
│ │ Button Device│ │ │
│ └──────────────┘ ┌─────────▼───────────┐ │
│ │ button-dashboard │ │
│ ┌──────────────┐ │ (React / Vite) │ │
│ │ Button Device│ └─────────────────────┘ │
│ └──────────────┘ browser │
│ │
└──────────────────────────────────────────────────────────────────┘
Devices and server all live on the same LAN. There is no cloud dependency for the core click loop.
Power on
│
▼
Saved WiFi credentials?
│
├─ YES → Connect to home WiFi → discover server → run event loop
│
└─ NO → Scan for "Button_Setup" AP
│
├─ AP found → join as setup FOLLOWER (UDP listener)
│
└─ AP absent → become setup LEADER (create AP + web server)
The first unconfigured device creates the Button_Setup access point and serves a web page on http://192.168.4.1. Other unconfigured devices that boot within the next second or two detect the AP and join as followers, announcing themselves over UDP port 4211.
The user connects their phone or laptop to Button_Setup (password: buttonsetup) and opens the setup page. The page shows all discovered devices, lets the user select the home WiFi SSID, enter the password, and assign a module type to each device. On submit, the leader broadcasts credentials to all followers over UDP, then all devices reboot and connect to the home network.
After connecting to the home WiFi, each device sends a DISCOVER_SERVER UDP broadcast on port 4210. The button-authenticator server responds with its address. Discovery retries until a response arrives or a configurable timeout expires.
UDP was chosen for discovery and lightweight event transmission because the devices are local-network appliances and the events are small. HTTP is still used where reliability, configuration, and dashboard integration matter more than low overhead.
Once a server address is known:
- Button presses are sent as UDP datagrams (device ID + button position).
- Mode updates can be pulled from the server via HTTP and written to the device's OLED display.
- The onboard BOOT button, held for 3 seconds, performs a factory reset (clears saved WiFi credentials).
The button matrix demo shows the event path from physical input to backend server log.
The firmware supports swappable module types declared at setup time:
| Module | Hardware |
|---|---|
button |
4×3 button matrix (up to 12 buttons), optional OLED, optional rotary encoder |
Additional module types can be added without restructuring the firmware.
A Node.js/Express 5 server with a SQLite database.
- Receive UDP broadcasts for server discovery and respond with the server address.
- Accept button click events from devices.
- Store and query device metadata, button metadata, and click history.
- Serve a REST API consumed by
button-dashboard.
| Method | Path | Description |
|---|---|---|
GET |
/devices |
List all device IDs |
GET |
/devices/detailed |
Full device info including buttons and modes |
GET |
/device/:deviceId |
Single device record |
POST |
/device/:deviceId |
Upsert device metadata (name, image, purpose, modes) |
GET |
/buttons/:deviceId |
List button IDs for a device |
GET |
/buttons/:deviceId/detailed |
Full button info |
GET |
/button/:deviceId/:buttonId |
Single button record |
POST |
/button/:deviceId/:buttonId |
Upsert button metadata |
POST |
/buttonClick |
Record a button press (with optional mode) |
Modes are an ordered list of strings associated with a device. They have no server-side ID — they are plain strings. The server can push an updated mode list to a device; the device stores it locally and displays the active mode on the OLED.
When a button that observes modes is pressed, the currently active mode is attached to the click record.
This demo shows the device refreshing its OLED mode list from the server.
A React 19 / Vite / Tailwind CSS single-page application.
| Page | Purpose |
|---|---|
| Dashboard | Live card grid of all buttons with click counts and activity histograms |
| Device Setup | Configure device names, button labels, images, and purposes |
| Reports | Historical activity with per-button and per-device breakdowns |
This demo shows the React dashboard used to review devices, buttons, and activity.
- Map — Physical layout of devices across a space; shows which buttons exist and where
- Pouches — User-defined groupings of buttons across any devices, with freeform drag-and-drop layouts (powered by dnd-kit)