Command Handling¶
sim-hw subscribes to the inklet/dev/{thingName}/down/cmd MQTT topic and processes incoming commands from the backend. Each command has a kind field that determines how it is handled.
Command Reference¶
| Kind | Display Action | Description |
|---|---|---|
text |
Renders text to 800x480 bitmap, pushes to sim-dashboard | User-sent text content for the e-ink display |
claim_code |
Renders large centered pairing code, pushes to display | 6-character code for device binding |
bound |
Shows "Device bound successfully" message | Notification that binding completed |
unbound |
Clears display, re-requests claim code via MQTT | Notification that the device was unbound |
already_bound |
No display action (logged only) | Response when requesting claim while already bound |
text¶
The primary command type. Renders user-provided text content to the simulated e-ink display.
MQTT Payload:
{
"kind": "text",
"id": "01912345-9999-7abc-def0-aaaaaaaaaaaa",
"text": "Hello from Inklet!\n\nThis is a multi-line message that will be word-wrapped to fit the 800×480 display."
}
Behavior:
- Receives the command payload from the
down/cmdtopic - Extracts the
textfield - Renders the text to an 800x480 1-bit (monochrome) bitmap using Pillow
- Word-wraps long lines to fit within the display width
- Exports the rendered image as a PNG
- HTTP POSTs the PNG to the sim-dashboard Fastify server
- sim-dashboard broadcasts the new framebuffer to connected WebSocket clients
Rendering Details:
- Canvas size: 800x480 pixels
- Color mode: 1-bit (black and white), matching real e-ink display capabilities
- Background: white (
1) - Text color: black (
0) - Font: system default (or bundled monospace font)
- Word wrapping: automatic, based on character width and canvas dimensions
- Padding: applied on all sides to prevent text from touching the edges
claim_code¶
Renders a 6-character pairing code prominently on the display. This code is entered by the user in the portal or sim-dashboard to bind the device.
MQTT Payload:
Behavior:
- Receives the claim code from the backend
- Renders the code in a large, centered font on the 800x480 canvas
- Includes instructional text (e.g., "Enter this code to pair your device")
- Pushes the rendered PNG to sim-dashboard
Display Layout:
┌──────────────────────────────────────────┐
│ │
│ │
│ Enter this code │
│ to pair your device │
│ │
│ A 3 X 9 K 2 │
│ │
│ │
│ │
└──────────────────────────────────────────┘
Tip
The claim code uses a monospaced, high-contrast font at a large size to ensure readability, even on the simulated display with e-ink effects applied by sim-dashboard.
bound¶
Displayed when the device is successfully bound to a user account.
MQTT Payload:
Behavior:
- Renders a confirmation message: "Device bound successfully"
- Pushes the rendered PNG to sim-dashboard
- The device is now ready to receive
textcommands from its owner
unbound¶
Received when the device owner unbinds the device. The device should clear its display and return to the pairing state.
MQTT Payload:
Behavior:
- Clears the current display content
- Publishes a
request_claimmessage toinklet/dev/{thingName}/up/request_claim - The backend generates a new claim code and sends it back as a
claim_codecommand - The device displays the new claim code, ready for re-pairing
This creates an automatic cycle: unbind triggers claim request, which triggers claim code display.
already_bound¶
Received when the device sends a request_claim message but is already bound to a user.
MQTT Payload:
Behavior:
- Logs the event:
INFO Device is already bound, ignoring claim request - No display action is taken
- The device continues normal operation
This typically happens if a device restarts and sends a request_claim before receiving its first heartbeat response. The backend recognizes the device is bound and sends already_bound instead of a claim code.
Display Rendering Pipeline¶
All commands that produce display output follow the same rendering pipeline:
Command received (MQTT)
│
▼
Text/code extracted from payload
│
▼
Pillow Image.new("1", (800, 480), 1) ← White background
│
▼
ImageDraw.text() with word wrapping ← Black text
│
▼
image.save(buffer, format="PNG") ← Export as PNG
│
▼
HTTP POST to sim-dashboard ← Push framebuffer
│
▼
WebSocket broadcast to all clients ← Live display update
Rendering with Pillow¶
sim-hw uses the Pillow library to generate 1-bit monochrome images. The core rendering logic:
from PIL import Image, ImageDraw, ImageFont
# Create a 1-bit white canvas
img = Image.new("1", (800, 480), 1)
draw = ImageDraw.Draw(img)
# Word-wrap and draw text
draw.text((20, 20), wrapped_text, fill=0, font=font)
# Export as PNG bytes
buffer = io.BytesIO()
img.save(buffer, format="PNG")
png_bytes = buffer.getvalue()
Pushing to sim-dashboard¶
The rendered PNG is sent to the sim-dashboard Fastify server via HTTP POST:
The Fastify server stores the framebuffer and broadcasts it to all connected WebSocket clients. The React frontend renders the image with realistic e-ink display effects.
Error Handling
If the HTTP POST to sim-dashboard fails (e.g., server is not running), sim-hw logs a warning and continues operating. The MQTT connection and heartbeats are not affected by display rendering failures.