IoT Devices
Open Genie includes a first-class IoT device subsystem that manages hardware integrations — ESP32 music boxes, Tuya smart plugs, and Bluetooth audio sinks — alongside its other features.
Distinct from Paired Devices: The term Devices in Open Genie refers to IoT hardware. Paired Devices (phones, tablets, TVs) is a separate feature at
/paired-devices.
Supported Hardware
| Kind | kind value | Connection |
|---|---|---|
| ESP32 Music Box | esp32_music_box | WebSocket (/ws/iot) |
| Tuya Smart Plug | tuya_plug | Tuya LAN protocol via tuyapi |
| Bluetooth Audio Sink | bluetooth_audio | Managed via the parent ESP32 |
Architecture
Browser / Mobile
│
▼
GET /api/iot/devices ← IoT device list + live state
GET /api/iot/devices/[id] ← Single device + recent events
POST /api/iot/control ← Direct actions (play, stop, plug on/off, …)
POST /api/iot/command ← Natural-language intent → action
GET /api/iot/status ← Live state snapshot
GET /api/iot/events ← Paginated iot_events log
│
▼
lib/iot/ ← Module boundary — nothing leaks out
ws-handler.ts ← ESP32 WS connection map
tuya-plug.ts ← Tuya singleton
intent.ts ← Claude intent classifier
speech.ts ← Whisper transcription (ESP8266 voice)
audio-converter.ts ← ffmpeg → PCM stream
songs.ts ← IotSong helpers (reads from media_files)
bootstrap.ts ← Auto-seeds known devices on boot
│
▼
PostgreSQL
iot_devices ← Device registry
iot_events ← Append-only event log
WebSocket — /ws/iot
ESP32 firmware connects here on boot:
ws://<server-ip>:3000/ws/iot?clientId=<mac-address>
The server matches clientId against iot_devices.externalId. If IOT_AUTO_PAIR is enabled (default on), unknown devices are auto-registered on first connect.
See WebSocket Protocol for the full message reference.
Song Library
IoT-playable audio is served from Open Genie's media library, not from a static JSON catalog. Any audio file under data/media/audio/ that has metadata.iotPlayable = true (set at ingest time) appears in the song list on an ESP32's detail page.
To add songs:
- Drop MP3s into
data/media/audio/genie-devices/. - Trigger a rescan:
GET /api/media/scan. - The new tracks appear in the song library automatically — no code changes needed.
Event Log
Every state change (connect, disconnect, play, stop, Bluetooth scan, plug toggle, …) is written to iot_events. The /events page surfaces these with filtering by device, event type, level, and time range.
iot_events rows older than 30 days are pruned automatically.
Settings
IoT settings live in the Settings → IoT Devices section of the web UI. They can also be supplied as environment variables (env takes lowest precedence):
| Setting / Env var | Description |
|---|---|
TUYA_DEVICE_ID | Tuya device ID |
TUYA_LOCAL_KEY | Tuya local encryption key |
TUYA_DEVICE_IP | Device LAN IP (optional — auto-discovered) |
TUYA_PROTOCOL_VERSION | 3.1 / 3.2 / 3.3 (default) / 3.4 |
IOT_AUTO_PAIR | Auto-register unknown ESP32s on first WS connect (true by default) |
Changing Tuya credentials in the UI and clicking Save reconnects the plug without a server restart.
Adding a New IoT Device
Via the UI
Open Devices (/devices), click + Add IoT Device, and choose the device kind. (Note: /paired-devices is the separate page for phones, tablets, and TVs.) Fill in the form — for an ESP32 you only need a display name (the clientId is sent by the firmware on connect). For a Tuya plug, enter the Tuya credentials.
Via the API
# Manually register an ESP32
curl -X POST http://localhost:3000/api/iot/devices \
-H "Content-Type: application/json" \
-d '{"name": "Living Room Music Box", "kind": "esp32_music_box", "externalId": "aabbccddeeff"}'
Module Boundary
All IoT code is isolated under lib/iot/ and server/routes/iot/. The only export consumed by other features is:
import { onIotEvent } from "@/lib/iot/events";
This allows future automation skills (e.g. "turn plug on at sunset") to emit events without coupling to IoT internals.