Skip to main content

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

Kindkind valueConnection
ESP32 Music Boxesp32_music_boxWebSocket (/ws/iot)
Tuya Smart Plugtuya_plugTuya LAN protocol via tuyapi
Bluetooth Audio Sinkbluetooth_audioManaged 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:

  1. Drop MP3s into data/media/audio/genie-devices/.
  2. Trigger a rescan: GET /api/media/scan.
  3. 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 varDescription
TUYA_DEVICE_IDTuya device ID
TUYA_LOCAL_KEYTuya local encryption key
TUYA_DEVICE_IPDevice LAN IP (optional — auto-discovered)
TUYA_PROTOCOL_VERSION3.1 / 3.2 / 3.3 (default) / 3.4
IOT_AUTO_PAIRAuto-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.