-
Notifications
You must be signed in to change notification settings - Fork 1
Matrix Gateway
A split-flap display with no split flaps.
This is the SplitFlap Gateway firmware ported to the Waveshare ESP32-S3-RGB-Matrix driver board driving a HUB75 RGB LED matrix. The RS-485 transceiver is gone; in its place is a software bus and a wall of virtual split-flap modules, each one emulating a real module's wire protocol byte-for-byte and rendering itself as a flapping character cell on the panel.
Repo: github.com/avandeputte/MatrixPortalGateway — versions up to v1.25.0 ran on the Adafruit MatrixPortal S3; that final MatrixPortal build lives on the
matrixportalbranch. The move to the Waveshare board (v2.0.0) is a hardware port only: the API, capabilities and companion behaviour are unchanged. v3.0.0 replaced the HTTP server (ESP-IDFesp_http_server, spoken natively) and addedGET /api/events— a Server-Sent Events stream that pushes the display state the instant the wall changes (theeventscapability token; the dashboard's live preview rides it). One breaking change came with it:POST /api/ota/uploadandPOST /api/fs/upload?name=<file>take the raw file as the request body (no multipart) —curl --data-binary @fileinstead ofcurl -F. v3.0.1 rounds it out: the dashboard preview renders the actual framebuffer while canvas/effects own the panel (and tells colour flaps from lowercase letters), companion-advertised tabs appear automatically, ops throughput went from ~8 fps to ~40 fps, and ops/ticker text is full UTF-8 → CP1252. v3.1.0 adds two panel-efficiency features the companion uses automatically: delta frames —PUT /api/canvas/rectsredraws only the rectangles that changed since the last frame instead of the whole panel — and a named sprite-atlas library (PUT /api/canvas/atlas/<name>,…/save), so a sheet of tiles uploads once and is reused (and optionally persisted across reboots) rather than re-sent every draw. Both cut WiFi traffic on the shared bus. v3.2.0 adds a persistent draw channel (PUT /api/canvas/stream— TLV records over one connection, no per-frame round trip, ~28 fps rect-delta animation), pushes the status JSON over the SSE stream so a connected dashboard needs no pollers, draws glyphs through the row blitter, and serves the dashboard pre-gzipped (54 KB → 17 KB on the wire). v3.3.0 rebuilds the Arduino core with WiFi/lwIP buffers in PSRAM — network traffic no longer competes with the firmware for internal RAM, quadrupling the worst-case heap margin under load (certified: 2 h adversarial soak, 0 reboots, 0 errors, 65 KB floor on the 256×64 board). v3.0 also removed MQTT and Home Assistant support from this gateway: the companion is pure REST and the dashboard is SSE-pushed, so the broker connection was pure weight on a RAM-tight board. (The physical SplitFlap Gateway keeps its full MQTT/HA surface — this applies to the Matrix Gateway only.)
Everything above the bus is the unmodified gateway: the same web UI, REST API and OTA (MQTT/Home Assistant support was removed in v3.0 — see the note above). The Companion drives the split-flap wall without any changes — and, because it asks the gateway what it is, also unlocks the extras a drawn wall adds: mixed-case text, pictographs, named colours, and the canvas it can draw on.
- Try the whole stack with no mechanical build — web UI, companion, the lot, on one driver board plus a panel. No modules, no RS-485, no steppers, no calibration.
- An LED-matrix display in its own right — full colour, real accented characters, lowercase, the colour flaps, and an animated flip effect with a physical-looking seam.
- A faithful test target — because it emulates at the protocol level, it exercises the same code paths a real gateway does.
| Part | Notes |
|---|---|
| Waveshare ESP32-S3-RGB-Matrix driver board | The controller — an ESP32-S3 with 32 MB octal flash and 16 MB octal PSRAM, USB-C, and a battery-backed PCF85063 RTC (SH1.0 cell socket). It plugs straight onto the panel's HUB75 header. |
| A HUB75 LED panel | Default layout is a 15 × 3 wall on a 128 × 32 chain (two 64×32 panels in series, or one native 128×32). Bigger panels give roomier cells. |
| A 5 V power supply | Sized for your panel — check the panel specs. |
64-row panels (128×64) use the HUB75 E address line, which this board wires directly on its HUB75 header — so a 64-row panel just works, with no jumpers or board changes. See the repo's README for the panel/size table.
Panel wiring varies one other way: some panels are BGR rather than RGB. Nothing can detect that — it shows up as every colour being wrong in a telltale pattern (red draws blue, yellow draws cyan, while green and white look fine). That's a setting, not a hardware fix: Settings → LED Panel → BGR.
On a large panel (say 256×64) the framebuffer can outgrow the ESP32-S3's internal SRAM at a
high bit depth. Rather than refuse and run headless, the driver auto-reduces the bit depth to
the deepest that fits — 256×64 lands on 3 bitplanes — and reports the real value as panel.depth
in GET /api/status. See Canvas API → Panel bit depth.
pio run -t upload # build + flash over USB-C
pio device monitor # 115200 baud, native USB CDCLater updates can also go over the air, from the web updater at /ota. There is no UF2
bootloader on this board — no drag-and-drop flashing, no double-tap reset. If the board ever
won't program, hold BOOT while plugging in (or while tapping RESET) and flash with
esptool / pio run -t upload.
WiFi works like the real SplitFlap Gateway — with one difference: its hostname is configurable, and the fallback AP is named after it.
- On first boot it raises an access point called
splitflap-gw-<6 hex digits>(derived from the board's MAC — e.g.splitflap-gw-a3f19c), password12345678. - Join it, open http://192.168.4.1, and save your SSID + password in Settings → WiFi.
- It joins your LAN and the AP shuts off. Reach it at http://.local — and you can change the hostname to something friendlier in Settings.
The same mDNS caveats apply: if .local doesn't resolve on your network, find the board's
IP in your router's DHCP client list (look for the hostname above) and use that instead —
see Finding it on your network. From there it's the
same web UI as the gateway — same tabs, same settings.
One nicety a physical gateway doesn't have: the board's battery-backed RTC (fit a cell in its SH1.0 socket) means the clock is valid seconds after power-on, before any network is up — NTP then disciplines it on every sync. Without a cell, the gateway simply waits for NTP as before.
-
Emulated: the display commands the gateway sends — show a character (
-), show a flap by index (+) and home (h), addressed by module id or broadcast. That is the whole contract now: frames flow one way, the modules act on them, and nothing replies. (Earlier firmware also modelled the bus'sv/Aidentity and batch queries and fabricated a stable serial number per module. With no physical bus to interrogate — every answer was a compile-time constant — that machinery was removed: a drawn module is just an id and its current flap, andGET /api/flap/modulesrows are simply{id, flapIndex, flapChar}.) -
The reel is bigger: nothing is drawn from a physical reel, so nothing is rationed. A virtual module carries 237 flaps where a real one carries 64:
flaps what they are 0–155 every Windows-1252 glyph — letters, digits, punctuation, accents 156–162 the seven colour flaps — red, orange, yellow, green, blue, violet, white 163–222 the 60 lowercase letters, accents included 223–236 14 pictographs — ♥ ♦ ♣ ♠ ☀ ☺ ♪ ● ■ ⌂ and the four arrows -
Not emulated: the mechanism. There's no stepper, Hall sensor or EEPROM, so calibration, diagnostics and provisioning are no-ops (the modules are "born" provisioned). You won't use the Calibration or Provision tabs here.
-
The flip is a rendering effect, not a physics simulation — you can cap or disable it.
The last two rows of that table are the interesting ones, and they need a word of explanation, because for a long time they were on the reel but unreachable.
The gateway's original display protocol carries one byte per character, and it has a
collision it can never fix: the byte for lowercase r already means RED. That is why
m38-r sets the red flap, and it is why, on that path, lowercase has to fold to uppercase and
a heart — which has no Windows-1252 byte at all — cannot be addressed by character in any
way. This is not a bug to be tidied up; it is the price of a protocol that spends seven of its
letters on colours.
Firmware 1.6 answers it by adding a second, index-addressed endpoint:
POST /api/display/cells
{ "start": 0, "step_ms": 15,
"cells": [ {"ch": "H"}, {"ch": "i"}, {"color": "red"}, {"skip": true} ] }
A cell names a character, a colour by name, a blank, or skip (leave that module
alone). Because a colour is now a name rather than a borrowed letter, i can simply be the
letter i — and flap 223 can be a heart.
The plain character protocol works byte for byte, and a physical wall is unaffected.
You do not have to do any of this yourself. The Companion asks the gateway
what it is (GET /api/config), and if it finds a Matrix Gateway on firmware 1.6 or newer it
uses the index API automatically — so apps render in mixed case, colours stay colours, and
pictographs appear. Point it at a physical wall and it falls back to the character protocol
and folds everything to capitals. Same app, same companion, whichever wall you own.
Everything above treats the panel as a wall of flaps. But it is also, physically, a grid of RGB LEDs — and the Matrix Gateway can hand that grid over as a plain framebuffer, bypassing the split-flap emulation entirely, so you can draw anything on it. A real split-flap gateway has no framebuffer at all, so this is the one place a Matrix wall does something the hardware it emulates simply cannot.
There is a lot here — full frames (rgb888, rgb565 or compressed QOI), partial-rectangle
updates, an on-device animation loop that plays from PSRAM after the client disconnects, a
scrolling ticker, JSON draw ops, six on-device effects (plasma, fire, matrix,
fliporama, clock, life) with hue/density parameters, and a framebuffer readback you
can poll for a live preview. A client discovers the whole surface from GET /api/capabilities — the
canvas, effects and effectParams keys, which a physical gateway never carries.
On-device effects and animations run on the panel itself with nothing on the network — pushing frames over HTTP sustains ~40 fps since fw 3.0.1 (near 8 fps before), while a panel effect is limited only by the display. While a canvas is active the reel renderer stands down; a split-flap command or Quiet Time reclaims the panel, and nothing canvas is persisted, so a reboot returns to the flaps.
→ Full reference: the Canvas API page. You rarely drive any of this by hand — the Companion exposes it through canvas apps: Effects, Image, Animation, Ticker, Lumina Clock, Weather Sky, Overview, Date Card, Stock Graph, Weather Panel, Scoreboard, HA Dashboard and Aquarium — plus dual-view apps that draw on the panel and run on flaps (Public Holidays, Countdown, World Clock) and channel/quiz apps that render as text + a themed icon — and any you write yourself.
The Matrix Gateway is a drop-in replacement for the modules and the gateway. Wherever the rest of this wiki says "the gateway," it applies here too — including registering the Companion. (Exception: the Home Assistant page is physical-gateway-only since Matrix Gateway v3.0 removed MQTT/HA support.) Skip the Hardware and Module Firmware pages entirely.
Next: Companion →
Start
Build it
- Hardware
- Module Firmware
- Provisioning
- Calibration
- Flaps & Character Sets
- SplitFlap Gateway
- Matrix Gateway
- LCD Gateway
Drive it
- Companion
- Built-in Apps
- Standalone & Docker
- Multiple Displays
- Home Assistant
- Vestaboard API
- MCP Server
- Using splitflap-os
Extend it
Reference
Hardware © Adam G Makes