Skip to main content
DEJA

Protocol Details

DEJA IO supports three communication protocols — USB serial, MQTT and WebSocket. All use JSON messages.

🔌 Serial USB

Direct wired connection between the DEJA Server and any of the USB-serial Arduino-family device types: deja-arduino (Arduino Mega) or deja-esp32 (ESP32 Dev Module). Both share the same firmware sketch and identical wire format.

Parameter Value
Baud rate 115200
Data bits 8
Stop bits 1
Parity None
Encoding UTF-8 JSON array

How It Works

  1. The board connects via USB to the server machine
  2. DEJA Server auto-detects the serial port
  3. Server opens the port at 115200 baud
  4. JSON command arrays are written to the port; responses are read line-by-line
  5. Auto-reconnect with exponential backoff (1s → 30s max) on disconnect

Wire Format

Commands arrive as a JSON array (multiple commands can be batched per write):

[
  { "action": "pin", "payload": { "pin": 8, "state": 1 } },
  { "action": "servo", "payload": { "servo": 0, "value": 90, "profile": "medium" } }
]

Sensor state changes are published back over the same serial line as single objects (e.g. { "sensor": 0, "state": 1 }), debounced by SENSOR_DEBOUNCE_MS (500 ms unless set in the device's advanced config).

Heartbeat

The board says {"status":"connected","device":"<id>"} at boot and sends {"status":"alive",…} every 60 s. The server writes the bare line ping every 30 s, starting 3 s after it opens the port (or at once when it reads the boot hello), and the board answers pong. The board's USB-serial status LED goes from OK to failed after 60 s without hearing the server. Redeploy older firmware: it doesn't understand ping.

Port Detection

The server lists available serial ports and matches them to registered devices. You can also manually specify a port path in your device configuration.

📬 MQTT over WiFi

Wireless publish/subscribe messaging via an MQTT broker (built into the DEJA Server). The default transport for both wireless device types: deja-mqtt (Raspberry Pi Pico W, CircuitPython) and deja-esp32-wifi (ESP32, Arduino C++). Both share the same topic scheme and the same single-object command format — the DEJA Server treats them identically on the wire.

Parameter Value
Default port 1883 (unencrypted)
Protocol MQTT v3.1.1
QoS 0 (at most once)
Encoding UTF-8 JSON object

Topic Structure

{TOPIC_ID}/{LAYOUT_ID}/{DEVICE_ID}          ← device subscribes (receives commands)
{TOPIC_ID}/{LAYOUT_ID}/{DEVICE_ID}/messages ← device publishes (sensor events, status)

TOPIC_ID is DEJA — the server always publishes on DEJA/…. Example for a device on the "tam" layout:

DEJA/tam/tj-eagle-nest-pico          ← commands IN
DEJA/tam/tj-eagle-nest-pico/messages ← sensor events / status OUT

Wire Format

Commands arrive as single JSON objects (one command per MQTT message) with a device field for filtering:

{
  "action": "pin",
  "device": "my-esp32-wifi",
  "payload": { "pin": 18, "state": 1 }
}

Sensor events the device publishes back to …/messages flow through the same pipeline as USB-serial sensors, so state updates show up in Throttle the same way.

Device Filtering

Multiple devices can share the same MQTT topic prefix. Each message includes a device field, and devices only process messages matching their own DEVICE_ID. This is true for both deja-mqtt and deja-esp32-wifi.

Broker Setup

Nothing to install — the DEJA Server runs its own MQTT broker on port 1883 whenever MQTT is enabled (the default). Point your devices at the server machine's LAN IP (the deploy wizard suggests it automatically, and deja config prints it).

Already running Mosquitto or another broker? The server detects a broker on the port (or an MQTT_BROKER pointing at another machine) and connects to it instead. Set MQTT_EMBEDDED=false in ~/.deja/.env to never start the built-in broker.

WebSocket (device transport)

The alternative to MQTT for deja-esp32-wifi and deja-mqtt (Pico W). Pick Transport → WebSocket when adding the device, then run deja deploy. The device dials the DEJA Server directly — no broker.

Parameter Value
Endpoint ws://{serverHost}:8082/device (port = VITE_WS_PORT, default 8082)
Encoding UTF-8 JSON, one value per text frame; binary frames are ignored
Frame limit 4 KiB — larger frames from a device are dropped
Heartbeat server pings every 30 s; a socket that missed the previous ping is closed
Reconnect device retries after 1 s, doubling up to 30 s. The backoff never sleeps, but one attempt to an unreachable server pauses the device's loop (servos, sensors) for up to ~1 s on the ESP32 (5 s from an Arduino IDE build) or ~10 s on the Pico W. No attempt starts during a turnout pulse

Handshake

The device sends two headers with the upgrade request:

Authorization: Bearer <token>
X-Deja-Device: <deviceId>

deja deploy generates the token (32 random bytes, 64 hex characters), bakes it into the firmware, and stores only its SHA-256 hash on the device in DEJA Cloud. The server answers HTTP 401 when the device ID is unknown in the layout, the device isn't set to WebSocket, it has no stored hash, or the token doesn't match (Rejected device … token mismatch in the server log). Run deja deploy --rotate-token to issue a new token; the old board is refused until you reflash it.

A second connection for the same device replaces the first (the old socket is closed with code 4000, reason replaced), so a rebooted board never waits for a stale socket.

Messages

The same shapes as MQTT:

Direction Frame Meaning
server → device { "action": "pin", "device": id, "payload": { "pin": n, "state": b } } set an output
server → device { "action": "servo", "device": id, "payload": { "servo": n, "value": deg, "current"?: deg, "profile"?: name } } move a servo
server → device { "action": "turnout", "device": id, "payload": { "turnout": idx, "state": b } } pulse a Kato turnout
device → server { "status": "connected", "device": id } hello, sent once after connecting
device → server { "device": id, "sensor": i, "state": 0|1 } sensor change
device → server any non-JSON string device log line (console only)

When the port is open

The server opens its WebSocket port only when it's needed:

  • The layout has a WebSocket device — the port opens at startup, or as soon as a device is added or switched to WebSocket, with no restart. In this mode only /device is served; every other path is refused with 401.
  • ENABLE_WS=true (or ws.enabled in ~/.deja/config.json) — the port opens at startup and also accepts non-device connections on other paths.

Once open, the port stays open until the server restarts. Keep it on the LAN — never port-forward or proxy it to the internet.

The Pico W also pings the server after 30 s of silence (the server stops pinging during standby) and reconnects if it hears nothing for 75 s. The ESP32 uses the WebSockets library's heartbeat: it pings every 15 s and reconnects after two unanswered pings.

WebSocket (reserved, non-device clients)

[!NOTE] No DEJA app connects to the server's WebSocket — device serial logs reach Throttle through DEJA Cloud. Non-device connections are accepted only when ENABLE_WS=true.

When enabled, the server sends an acknowledgment on connect and fans out server broadcasts:

{ "action": "ack", "payload": { "layoutId": "tam", "serverId": "DEJA" } }

It accepts no client commands. WS_AUTH_TOKEN and WS_ALLOWED_ORIGINS apply to these connections only; /device always requires a device token. See apps/server/WEBSOCKET_PROTOCOL.md for the connection details.