Install
openclaw skills install openclawpanelControl an OpenClaw LED panel (64x32 HUB75 on ESP32-S3) over HTTP — display text, graphics, shapes, play sounds, and read status.
openclaw skills install openclawpanelControl an OpenClaw LED panel (64x32 pixels, RGB565) connected to your local network via HTTP.
The panel runs an HTTP server on port 80. Base URL: http://${OPENCLAW_PANEL_IP}.
Use curl -s with -H "Content-Type: application/json" for all POST requests.
"0xF800") or HTML hex ("#FF0000")arial_20 (largest), arial_18, arial_16, mono_18, mono_12, 5x7, 4x6 (smallest)"0xF800" red, "0x07E0" green, "0x001F" blue, "0xFFE0" yellow, "0xFFFF" white, "0x0000" black, "#FF6600" orangePOST /api/display/text — Show text.
{"text": "Hello", "x": 0, "y": 16, "color": "0x07E0", "font": "arial_20", "align_right": false, "swap": true}
All fields except text are optional (defaults: x=0, y=0, color=white, font=mono_12, align_right=false, swap=true).
POST /api/display/number — Show a formatted number (large values auto-format: 1000000 → "1 M").
{"value": 64770, "x": 0, "y": 20, "color": "0xFFFF", "font": "arial_20", "align_right": false, "swap": true}
POST /api/display/clear — Clear the display to black. No body needed.
POST /api/display/pixel — Draw pixels. Limit: ~400 pixels per call — larger payloads cause "Invalid JSON" on the ESP32. For filled areas, prefer rect, circle, or line endpoints instead.
{"pixels": [{"x": 10, "y": 5, "color": "0xF800"}, {"x": 11, "y": 5, "color": "0x07E0"}], "swap": true}
POST /api/display/line — Draw a line between two points (Bresenham).
{"x0": 0, "y0": 0, "x1": 63, "y1": 31, "color": "0xF800", "swap": true}
All fields except coordinates are optional (defaults: color=white, swap=true).
POST /api/display/rect — Draw a rectangle.
{"x": 0, "y": 0, "w": 64, "h": 32, "color": "0x001F", "fill": true, "swap": true}
fill defaults to true. Set false for outline only.
POST /api/display/circle — Draw a circle.
{"cx": 32, "cy": 16, "r": 10, "color": "0xFFE0", "fill": false, "swap": true}
fill defaults to false (outline). Set true for filled circle.
POST /api/display/graph — Draw a line graph.
{"values": [1.0, 3.5, 2.0, 7.0, 5.5], "x": 0, "y": 8, "w": 64, "h": 24, "color_line": "0x07E0", "color_fill": "0x07E0", "fill": true, "swap": true}
Values are auto-scaled to fit the height. fill fills the area under the line.
POST /api/display/panel — Show a full dashboard panel (label + value + percentage + graph).
{"label": "BTC", "value": 64770, "percent": 3.5, "values": [60000, 61000, 63000, 64770], "color_line": "0xFFE0", "color_fill": "0xFFE0", "fill": true, "dissolve": true}
This clears the screen and draws a complete panel. dissolve enables a fade transition.
POST /api/display/gif — Play the built-in animated GIF. No body needed. Plays the built-in OpenClaw logo animation. Requires lock + clear before calling, otherwise nothing appears.
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/lock
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/clear
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/gif
POST /api/display/bitmap — Draw a raw RGB565 bitmap. Body is raw binary (wh2 bytes). Dimensions via query params.
POST /api/display/bitmap?x=0&y=0&w=16&h=8&swap=1
Content-Type: application/octet-stream
Body: <raw RGB565 bytes, little-endian, row-major>
Max size: 64x32 (4096 bytes). Each pixel is 2 bytes RGB565.
POST /api/display/dissolve — Smooth fade transition from current display to the back buffer.
{"steps": 10, "ms_per_step": 30}
Draw new content with "swap": false first, then call dissolve to transition. Both fields optional (defaults: steps=10, ms_per_step=30).
POST /api/display/brightness — Set display brightness.
{"value": 80}
Range: 0 (off) to 100 (max). Persists across reboots.
GET /api/display/framebuffer — Returns 4096 bytes of raw RGB565 binary data (64x32 pixels, row-major).
POST /api/display/lock — Take exclusive control of the display. Pauses the normal panel timeline. No body needed. Auto-unlocks after 30 seconds of inactivity.
POST /api/display/unlock — Release control back to the normal timeline. No body needed.
POST /api/view_panel — Force a specific timeline panel to show.
{"index": 0}
This also unlocks the display if it was locked.
POST /api/sound/play — Play a built-in sound. Blocks until playback finishes.
{"name": "coin"}
Available sounds: order (cash register), coin (Mario coin), epicwin (victory fanfare), youwin (short win).
POST /api/sound/wav — Upload and play a custom WAV file. Body is raw WAV data (max 64 KB). Non-blocking (plays in background).
POST /api/sound/wav
Content-Type: application/octet-stream
Body: <raw WAV file bytes>
WAV format requirements (strict):
To convert any audio file to a compatible WAV, use ffmpeg:
ffmpeg -i input.mp3 -ar 11025 -ac 1 -sample_fmt s16 -t 2.9 output.wav
GET /api/sound/list — Returns JSON array of built-in sound names.
Response: ["order","coin","epicwin","youwin"]
POST /api/sound/volume — Set speaker volume. Persists across reboots.
{"value": 50}
Range: 0 (mute) to 100 (max).
GET /api/sound/status — Returns current volume and mute state.
Response: {"volume": 50, "muted": false}
GET /api/status — Returns device info.
Response: {"version": "1.7.1", "ip": "192.168.1.50", "brightness": 80, "volume": 50, "display": {"w": 64, "h": 32}, "ws_connected": true, "api_locked": false}
swap parameterMost draw endpoints accept "swap": true|false (default: true).
true: the drawing appears on screen immediately after the call.false: the drawing is buffered but not shown yet. Use this to compose multiple elements before displaying them all at once. Send a final call with "swap": true to show everything.To show something temporarily, use lock/draw/unlock:
POST /api/display/lock
POST /api/display/clear
POST /api/display/text {"text":"HELLO","x":5,"y":20,"color":"0x07E0","font":"arial_20"}
... (display stays until unlock or 30s timeout)
POST /api/display/unlock
To compose a scene (multiple elements), use swap=false for all but the last:
POST /api/display/lock
POST /api/display/clear
POST /api/display/rect {"x":0,"y":0,"w":64,"h":10,"color":"0x001F","fill":true,"swap":false}
POST /api/display/text {"text":"BTC","x":2,"y":8,"color":"0xFFFF","font":"4x6","swap":false}
POST /api/display/text {"text":"64,770","x":2,"y":24,"color":"0xFFE0","font":"arial_16","swap":true}
POST /api/display/unlock
Always use "swap": false on every element except the very last one. This prevents flicker and ensures the entire scene appears at once.
To show something for a fixed duration (auto-unlock pattern):
POST /api/display/lock
POST /api/display/clear
POST /api/display/text {"text":"ALERT","x":4,"y":22,"color":"0xF800","font":"arial_20"}
sleep 5
POST /api/display/unlock
Use this when the user wants to flash a message briefly. Always unlock after the sleep.
For sounds, just call directly (no lock needed):
POST /api/sound/play {"name":"coin"}
To return to normal operation: call unlock, or use view_panel to jump to a specific dashboard panel.
mono_12 or 4x6 for informational text — they are more legible than large fonts on a 64x32 display. Reserve arial_20 for single words or short numbers.Font sizing for typical short text (4-6 chars):
arial_20 — too large for most use cases, text overflows or looks crowdedarial_16 — still large, ok for 1-3 charsmono_12 — good default for short words (4-6 chars)5x7, 4x6 — use for longer strings or when showing multiple linesCentering tips for mono_12:
User: "Show HELLO in green on the panel"
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/lock
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/clear
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/text -H "Content-Type: application/json" -d '{"text":"HELLO","x":18,"y":12,"color":"0x07E0","font":"mono_12"}'
Then after a few seconds: curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/unlock
Note: arial_20 is too large for 5-char words on a 64x32 display. mono_12 at y=12 centers vertically.
User: "Play the coin sound"
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/sound/play -H "Content-Type: application/json" -d '{"name":"coin"}'
User: "Draw a red circle in the center"
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/lock
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/clear
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/circle -H "Content-Type: application/json" -d '{"cx":32,"cy":16,"r":12,"color":"0xF800","fill":true}'
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/unlock
User: "Show Bitcoin at 64,770 with a chart"
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/lock
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/panel -H "Content-Type: application/json" -d '{"label":"BTC","value":64770,"percent":2.3,"values":[60000,61500,63000,64770],"color_line":"0xFFE0","color_fill":"0xFFE0","fill":true,"dissolve":true}'
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/unlock
User: "Set brightness to 30%"
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/brightness -H "Content-Type: application/json" -d '{"value":30}'
User: "Draw a diagonal red line"
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/lock
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/clear
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/line -H "Content-Type: application/json" -d '{"x0":0,"y0":0,"x1":63,"y1":31,"color":"0xF800"}'
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/unlock
User: "Smooth transition to new content"
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/lock
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/clear
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/text -H "Content-Type: application/json" -d '{"text":"NEW","x":10,"y":22,"color":"0x07E0","font":"arial_20","swap":false}'
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/dissolve -H "Content-Type: application/json" -d '{"steps":15,"ms_per_step":40}'
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/unlock
User: "Play a custom sound" (requires a local WAV file)
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/sound/wav --data-binary @mysound.wav
User: "What sounds are available?"
curl -s http://$OPENCLAW_PANEL_IP/api/sound/list
User: "Back to normal" / "Reset the panel"
curl -s -X POST http://$OPENCLAW_PANEL_IP/api/display/unlock