š Pico W Setup
The Raspberry Pi Pico W (deja-mqtt) connects to your layout wirelessly over WiFi and talks to the DEJA Server over MQTT (the default) or WebSocket ā ideal for devices placed inside scenery or buildings. Pick the transport under Transport when you add the device; deja deploy writes the matching settings.toml.
š See also:
deja-esp32-wifiā the ESP32-based WiFi/MQTT alternative. It uses the same MQTT topology (DEJA/{LAYOUT_ID}/{DEVICE_ID}for commands,ā¦/messagesfor sensor events), the same WebSocket endpoint, and the same single-object command format as the Pico W. Pick whichever hardware you have on hand: Pico W if you prefer CircuitPython and a smaller board, ESP32 if you prefer the Arduino C++ toolchain and more GPIOs.
Hardware Requirements
- Raspberry Pi Pico W (~$6)
- USB-C cable for initial setup and power
- Adafruit PCA9685 16-Channel PWM Servo Driver (optional, for servos)
- WiFi network on the same network as your DEJA Server
- DEJA Server running ā it provides the MQTT broker (port 1883) or, for WebSocket devices, the device endpoint (port 8082)
Wiring the PCA9685
| PCA9685 Pin | Pico W Pin |
|---|---|
| VCC | 3V3 |
| GND | GND |
| SDA | GP0 |
| SCL | GP1 |
Installing CircuitPython
- Download CircuitPython 9.2.9 for Pico W: adafruit-circuitpython-raspberry_pi_pico_w-en_US-9.2.9.uf2. Use version 9, not the newest 10.x: DEJA's bundled libraries are built for CircuitPython 9.
- Hold the BOOTSEL button while plugging the Pico W in. It shows up as a drive called RPI-RP2.
- Drag the
.uf2file onto RPI-RP2. - The Pico W restarts as CIRCUITPY. From now on, plug it in normally (no button) and run
deja deploy.
š§ On a Raspberry Pi without a desktop, USB drives don't mount on their own.
deja deploytriesudisksctl mountfor you. If that's not installed, find the drive withlsblkand mount it yourself (nosudoneeded from DEJA).šŖ On Windows, the wizard finds the drive labelled CIRCUITPY by itself.
Configuration
WiFi and transport ā settings.toml
deja deploy generates settings.toml for you. This is what it writes (a WebSocket device shown; an MQTT device has TRANSPORT = "mqtt" and a filled-in MQTT_BROKER):
# š DEJA Pico W Configuration
# Generated for device: my-pico-device
CIRCUITPY_WIFI_SSID = "YourWiFiNetwork"
CIRCUITPY_WIFI_PASSWORD = "YourWiFiPassword"
ENABLE_CONFIG = "true"
ENABLE_PWM = "true"
# š Transport: "mqtt" or "websocket"
TRANSPORT = "websocket"
MQTT_BROKER = ""
MQTT_PORT = "1883"
WS_HOST = "192.168.1.100"
WS_PORT = "8082"
WS_TOKEN = "<64 hex characters from deja deploy>"
LAYOUT_ID = "your-layout-id"
DEVICE_ID = "my-pico-device"
TOPIC_ID = "DEJA"
TRANSPORT = "mqtt"needsMQTT_BROKER(the DEJA Server machine's LAN IP).TRANSPORT = "websocket"needsWS_HOSTandWS_TOKEN; the other transport's keys are ignored.WS_TOKENis generated bydeja deploy; only its hash is stored on the device in DEJA Cloud. Don't share it.ENABLE_PWMis"true"when servo turnouts are assigned to the device, unless the device's ownenablePwmsetting says otherwise ā an explicit setting wins.
Pins, turnouts and sensors ā config.json
config.json maps logical pin numbers to physical GPIO names, and lists the device's Kato turnouts and sensors:
{
"pins": {
"8": "GP8",
"9": "GP9"
},
"turnouts": [
{ "index": 0, "straight": "GP14", "divergent": "GP15", "pulseMs": 250 }
],
"sensors": [
{ "index": 0, "pin": "GP20", "debounceMs": 100 }
]
}
Turnouts and sensors
- Turnouts are the layout's Kato turnouts assigned to this device, in the same order the ESP32 uses. A
turnoutcommand withstate: falsepulses thestraightpin,truepulses thedivergentpin, forpulseMs(250 ms). Servo turnouts don't appear here ā they're driven withservocommands. - Sensors are the layout's sensors assigned to this device. When a pin changes, the Pico sends
{ "device": "<id>", "sensor": <index>, "state": 0|1 }using the sensor's ownindex.debounceMs(default 100 ms) is the minimum time between two reported changes of the same sensor ā like the ESP32'sSENSOR_DEBOUNCE_MS, not a hold time.
Pulses and debounce never sleep: servos keep moving and sensors keep being read while a pulse runs or while the Pico waits between reconnect attempts. A single connect attempt to an unreachable host does pause the loop ā for up to about 10 s on WebSocket (a 5 s socket connect plus a 5 s wait for the handshake reply) ā so the Pico never starts one while a turnout pulse is running. A sensor change that happens while disconnected is sent after the Pico reconnects.
Deploying the Firmware
Copy all files from io/src/deja-pico-w/ to the CIRCUITPY drive:
code.pyā boot, WiFi and the main loopcommands.pyā pin, servo and turnout commands and sensor pollingtransport_mqtt.py/transport_ws.pyā the two transportsconfig.jsonā pins, turnouts and sensorslib/ā Adafruit libraries (MQTT, motor, servo, LED animation),deja_ws.py, the WebSocket client, andbackoff.py, the reconnect schedule both transports share
Or use the DEJA deploy script:
cd io
pnpm deploy
The script generates settings.toml and config.json from your device configuration in DEJA Cloud and copies everything to the mounted Pico W.
MQTT Topics
With TRANSPORT = "mqtt" the Pico W subscribes and publishes to topics based on its identity:
- Subscribe:
DEJA/{LAYOUT_ID}/{DEVICE_ID}ā receives commands - Publish:
DEJA/{LAYOUT_ID}/{DEVICE_ID}/messagesā sends status and sensor events
WebSocket
With TRANSPORT = "websocket" the Pico W opens ws://{WS_HOST}:{WS_PORT}/device, sending its token and device ID as headers, and receives the same commands over that connection. If the connection drops it retries after 1 s, doubling up to 30 s; no attempt starts while a turnout pulse is running. It pings the server after 30 s of silence and reconnects if it hears nothing for 75 s. See Protocol Details.
Command Format
Pico W devices receive JSON objects over MQTT or WebSocket:
{
"action": "pin",
"payload": { "pin": 8, "state": 1 },
"device": "my-pico-device"
}
The device field filters messages ā only commands matching this device's ID are processed. See the Command Reference for all supported actions.
Status display (optional)
A 0.96" 128Ć64 SSD1306 OLED on the Pico's I²C pins ā SDA ā GP0 (pin 1), SCL ā GP1 (pin 2), GND ā pin 3, VCC ā 3V3(OUT) (pin 36) ā shows the board's WiFi, server link and servo-driver state plus stats such as its IP and uptime. The Pico draws it itself, so it is right even when the server can't reach the board. Turn it on under the device's Firmware ā Display in Throttle, choose the rows, save, and copy the regenerated config.json onto the CIRCUITPY drive (or run deja deploy). The display shares the bus with the PCA9685; the Pico firmware fixes I²C to GP0/GP1.