CircuitPython firmware for the Adafruit MatrixPortal S3 — the LED matrix display device.
The Adafruit MatrixPortal S3 is an ESP32-S3 based board designed for driving HUB75 RGB LED matrices over WiFi.
| Spec | Value |
|---|---|
| Microcontroller | ESP32-S3 (dual-core, 240 MHz) |
| RAM | 8 MB |
| WiFi | 802.11b/g/n (integrated) |
| Matrix connector | 16-pin HUB75 header |
| Power (matrix) | Separate 5V supply required for the LED panel |
| USB | USB-C (programming, REPL console) |
| Firmware | CircuitPython |
The board connects directly to a standard 64×64 HUB75 LED matrix panel. The matrix requires its own 5V power supply — the MatrixPortal cannot power the panel from USB alone.
On boot, the device:
wifi.txt on the CIRCUITPY filesystemdeskradar-portal.localOnce running, it passively waits for draw commands from the deskradar application on the Pi. A watchdog timer reboots the device if no request arrives within 60 seconds.
Credentials are stored in src/wifi.txt on the CIRCUITPY volume:
deskradar deskradaradsb
Line 1 is the SSID, line 2 is the password. When the Pi configurator switches WiFi networks, it writes new credentials to this file automatically. The device reads wifi.txt on every boot.
wifi.txt is missing or malformed, the device reboots. Ensure the file always has exactly two non-empty lines.All endpoints are on port 80. The device's IP is assigned by DHCP; it is also reachable as deskradar-portal.local on the same network.
Health check. Returns the device's IP address.
GET /ping HTTP/1.1 HTTP/1.1 200 OK Content-Type: text/plain 10.42.0.51
Clears the display and renders a new frame. Accepts a JSON array of pixel objects.
POST /draw HTTP/1.1
Content-Type: application/json
[
{"x": 32, "y": 32, "r": 255, "g": 255, "b": 255},
{"x": 33, "y": 32, "r": 200, "g": 200, "b": 200}
]
Each object requires x, y (0–63), and r, g, b (0–255). Coordinates outside the 64×64 bounds are silently ignored. Colors are converted to RGB565 internally for matrix rendering.
Clears the entire display to black. Used when there are no aircraft in the store.
POST /empty HTTP/1.1 HTTP/1.1 200 OK
Keepalive. Resets the watchdog timer without modifying the display. Sent when the pixel data is identical to the previous cycle.
POST /no-change HTTP/1.1 HTTP/1.1 200 OK
Registers pixels that persist across all subsequent /draw calls. Useful for reference marks or calibration points that should always be visible regardless of what is drawn.
POST /add-constant HTTP/1.1
Content-Type: application/json
[
{"x": 32, "y": 32, "r": 100, "g": 100, "b": 100}
]
Constant pixels are layered on top of each rendered frame. To clear them, send an empty array.
The device monitors two conditions and reboots if either triggers:
| Condition | Action |
|---|---|
| No HTTP request received for 60 seconds | Reboot (re-initialise WiFi and server) |
| WiFi connection drops | Reboot (reconnect and re-advertise mDNS) |
The 60-second timeout means the deskradar app must send at least a /no-change request each cycle to keep the device alive. With the default 1-second POLL_CADENCE, this is well within the limit.
These are hardcoded in src/code.py and require a firmware update to change:
| Constant | Default | Description |
|---|---|---|
SERVER_PORT | 80 | HTTP server port |
REQUEST_BUFFER_SIZE | 32768 | HTTP request buffer (32 KB) |
DISPLAY_WIDTH | 64 | Matrix width in pixels |
DISPLAY_HEIGHT | 64 | Matrix height in pixels |
BIT_DEPTH | 5 | Color bit depth (5-bit = 32,768 colors) |
WIFI_CONNECT_TIMEOUT_S | 10 | Per-attempt WiFi connect timeout (seconds) |
MAX_WIFI_CONNECT_ATTEMPTS | 5 | Max WiFi connection retries before reboot |
REQUEST_TIMEOUT_S | 60 | Watchdog: reboot if no request in this window |
SCROLL_DELAY_S | 0.05 | Status text animation frame delay |
The device accepts standard 8-bit RGB values (0–255) in the JSON payload. Internally, these are converted to RGB565 (5 bits red, 6 bits green, 5 bits blue) for the matrix framebuffer:
def rgb_to_rgb565(r, g, b):
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
/Volumes/CIRCUITPY/)CIRCUITPY in Finder (or /Volumes/CIRCUITPY/)src/wifi.txt with your WiFi credentials if neededcd /path/to/deskradar-matrixportals3 ./install.sh
src/ to the device. The device reboots automatically and begins executing code.py.install.sh is a single command:
cp -r src/* /Volumes/CIRCUITPY/
Connect to the CircuitPython serial console (e.g. via screen /dev/tty.usbmodem* 115200) to see boot output including the assigned IP address. You can also ping deskradar-portal.local from any device on the same network once it has connected.
| Path | Contents |
|---|---|
src/code.py | Main application (476 lines) |
src/wifi.txt | WiFi SSID and password |
src/lib/adafruit_httpserver/ | HTTP server library (11 compiled modules) |
src/lib/adafruit_display_text/ | Text rendering (5 compiled modules) |
src/lib/adafruit_matrixportal/ | Matrix display abstraction (4 compiled modules) |
src/lib/adafruit_bitmap_font/ | Font support (7 compiled modules) |
src/lib/adafruit_ticks.mpy | Timing utilities |
src/lib/neopixel.mpy | NeoPixel LED support |
All libraries are pre-compiled (.mpy bytecode) and included in the repository — no additional package management is required.