A 128x64 RGB LED wall panel on the Waveshare ESP32-S3-RGB-Matrix board. It started as Keralots/AnimatedPixelClock, a retro-arcade clock, and all of that is still here. This fork adds live screens fed from the network, Lua effects uploaded over WiFi, a knob, an infrared remote and a carousel, Home Assistant over MQTT, text in Latin and Cyrillic on every screen, and an SDK that lets an AI agent drive the panel.
Host renders of the panel's own frames, 128x64 scaled 3x. Top row: the Lua aquarium, the world clock, the flight board. Middle: the rail board, the market dashboard, the media player. Bottom: the football clock, the room radar and the Tetris clock.
- Flash it from the browser: nickoscope.github.io/AnimatedPixelClock
- Releases: github.com/NickoScope/AnimatedPixelClock/releases
- OCEANARIUM, the tank on the panel and how it works: in English · по-русски
- Report a bug: issues
| Part | Notes |
|---|---|
| Waveshare ESP32-S3-RGB-Matrix | ESP32-S3-WROOM-2 N32R16V: 32 MB octal flash, 16 MB octal PSRAM, a 2x8 HUB75 socket with on-board buffers, two USB-C sockets (USB and POWER), BOOT and RESET buttons, and an SHTC3 temperature and humidity sensor |
| 2x 64x64 HUB75E panels | Chained OUT to IN with the ribbon into one 128x64 canvas, 1/32 scan. The panels here are RGB-Matrix-P2-64x64-B with FM6124HJ column drivers; the firmware initialises them in FM6126A mode |
| 5V power | Each panel has its own power harness. The board has a USB-C socket marked POWER and 5V/GND posts |
| EC11 rotary knob (optional) | One knob with a push switch. Without it the carousel runs the panel by itself |
This is the only board this fork is tested and released for. The firmware still builds for the boards upstream supports (ESP32-S3-WROOM-1 devkit, ESP32-S3-Zero, Super Mini, wired by hand to the panels), but none of those has been flashed here; for them, see upstream and its wiring guide.
The pages:
| Page | What it shows | Needs | Details |
|---|---|---|---|
| Clock | Fourteen animated clock styles, see Clock styles | nothing | this file |
| World clock | A dotted world map with day, night and twilight, your cities, and home's time in home's zone | nothing | src/worldclock |
| Flight board | Departures and arrivals for any airport, and flights you track | a FlightAware AeroAPI key, or Home Assistant | src/flightboard |
| Rail board | Departures and arrivals for any National Rail station | a Realtime Trains token, or Home Assistant | src/railboard |
| Yacht radar | Live AIS vessels in the Bay of Cannes: a sweeping plot and a vessel table | an aisstream.io key | src/yachtradar |
| Media | Now playing on a Home Assistant or Music Assistant player, and the knob as its remote | Home Assistant, MQTT | src/media |
| Markets | Indices, ticker, portfolio and holdings pages with an exchange tape | Home Assistant, MQTT | src/market |
| Lua effects | Up to 36 on a panel, loaded over the air; none are compiled in since 2.6.0. The gallery has the ones that used to be (football, snooker, snake and Tetris clocks, Minecraft, the room radar, La Gioconda) and more | nothing; the room radar needs a presence sensor over MQTT | src/lua, gallery |
The weather clock, the ambient screensavers, custom GIF animations, the PC monitor and the audio visualizer from upstream are all still in; they are described further down.
The board's own SHTC3 sensor reports temperature and humidity in /api/info, on
the portal's Clock page, and, on request, as two Home Assistant sensors
(src/climate).
A new screen is a Lua script, sent to the running panel in about a second: no build, no flash, no reboot. The panel keeps 36 effects, each as big as it needs (the filesystem's room, up to 512 KB since 2.6.3); since 2.6.0 none are compiled into the firmware, so a freshly flashed panel starts with none and you add them from the portal's "Add from the gallery". They survive reboots and OTA updates.
python3 tools/agent/gallery.py show aquarium # one of the gallery's screensFrom the web portal too (2.5.7), on the Effects & clips page: each effect has a switch that keeps it in or out of the knob's walk and the carousel, uploaded ones have Delete, and Add from the gallery lists this repository's gallery with previews and sends the one you pick to the panel; the browser fetches it from GitHub, the panel never goes out for it.
The gallery has an aquarium that reacts to people in the
room, OCEANARIUM (a public aquarium's tank of over a hundred kinds of sea life,
how it works),
golf on real courses, a starship, the Bay of Cannes and La Gioconda. Scripts are written and
checked on the host first with tools/luasim, which runs the
same API the panel does.
Everything the panel draws as text is UTF-8, in both of its fonts (the classic
5x7 and the small Picopixel), capitals and lowercase: banners, cards, media
titles, the boards, city and airport names, the original clock screens, and Lua
effects (px.text(x, y, s, r, g, b, "5x7")). Write Russian as it is. The
Cyrillic comes from the public-domain X11 misc-fixed fonts, sized to the Latin
it stands beside; anything the fonts cannot draw shows as a solid block rather
than vanishing. Details and tests: AGENTS.md, section 8.
Turn the knob to walk every clock style, then every page. Click enters a page that has controls of its own, such as the media remote, and click again leaves it. An infrared receiver on the BOOT button's line adds a remote beside the knob: ten buttons you teach in the portal, each with a function you choose (src/ir). Leave the knob alone for 60 seconds and the carousel takes over, showing each page and each clock style for 15 seconds. Both numbers can be changed in the web portal.
The panel talks to Home Assistant over MQTT, through one connection shared by
every page. It has no default broker: it connects only after you give it one in
the portal. Without Home Assistant the clock, world clock, flight board, rail
board, yacht radar and Lua effects all work on their own; the media and market
pages and the room radar need it. The Home Assistant side of each page (the
AppDaemon apps) lives next to it under tools/.
tools/agent/ is a Python SDK and an MCP server
(over stdio) that let Claude Code, Codex or any MCP client
find panels on the network by MAC address, bring a new one up, switch pages,
read what the panel is doing, and write and upload new Lua screens.
AGENTS.md is where an agent starts. It deliberately has no
flashing tool: building and flashing is a person's call.
| ID | Style | Description |
|---|---|---|
| 0 | Mario | Mario jumps to bounce changed digits; optional idle enemy encounters |
| 1 | Standard | Traditional digital clock with date |
| 2 | Large | Extra-large digits |
| 3 | Space Invaders | Shoots lasers to change digits; choose an invader or spaceship character |
| 5 | Pong / Arkanoid | Breakout-style ball physics, digits shatter and reassemble |
| 6 | Pac-Man | Pac-Man eats pellet-based digits |
| 7 | Snake | Nokia-style snake hunts pellets left by changed digits |
| 8 | Tetris | Block digits rebuilt by slabs or falling dots, idle falling blocks with a separate configurable color per shape; optional small corner-clock mode hands the whole panel to an auto-played game with a much taller stack |
| 9 | Cycle All Styles | Choose enabled styles, their order and duration in Clock settings; Weather is skipped until configured |
| 10 | Asteroids | Wireframe ship shoots changed digits into spinning line shards |
| 11 | Dino Runner | A T-Rex runs and jumps cacti; a pterodactyl swaps changed digits |
| 12 | Matrix Rain | Digital rain with fading glyph trails; changed digits decode out of the rain |
| 16 | TRON | Two neon light cycles leave fading trails, avoid walls and crash into sparks; one traces changed digits as a continuous line |
| 15 | Bomberman | Brick digits explode with cross-shaped blasts and rebuild; a tiny hero navigates between digits, bombs crates and collects bonuses |
| 14 | Weather Clock | Time plus live local weather: animated condition icon, temperature, daily range, humidity, sunrise/sunset |
Select Bomberman in Clock > Clock style and save. Its digit color is configurable in Colors. It respects 12/24-hour time and colon blinking. The hero follows corridors around the digits, chooses different bombing spots, and retreats to safety. Blast arms stop at the first digit brick or crate. In Cycle All, enable Bomberman and set its duration; existing rotations keep their order and durations, with Bomberman initially disabled.
Select TRON in Clock > Clock style and save. Colors controls the time digits and both light cycles. It supports 12/24-hour time and colon blinking. Under TRON settings, Motorcycle variant selects Motorcycle (side view) (the default) or Light cycle (top view). Save to keep the choice across reboots; it also applies when TRON runs in Cycle All. Enable TRON separately in Cycle All; existing rotations retain their settings with the new style initially disabled.
ID 4 is a legacy alias for the Space Invaders renderer and is not a separate choice in the web interface. ID 13 is retired; use the IDs listed above.
Style colors are editable in the web interface (digits, characters, effects, backgrounds), so each clock can match your setup.
The style names describe what each animation is styled after. This project is not affiliated with or endorsed by the rights holders; see Trademarks and attribution.
Once on WiFi, open the device's IP address or http://pixelclock.local in a browser:
- Clock settings: style, 12/24 hour, date format, position, per-style animation options, per-element colors with one-click reset to defaults
- Display: brightness (live slider), colon blink mode/rate, adaptive refresh rate, scheduled night dimming (start/end time to the minute + dim level) and a scheduled power-off window that blanks the panel overnight to spare the LEDs
- Audio visualizer: effect style, its colors and the oscilloscope options
- Timezone: built-in region list with automatic DST transitions (POSIX TZ rules, no manual toggles)
- Network: DHCP or static IP, device name (mDNS), show IP at boot, NTP time servers (see below)
- PC monitor layout: which metrics are visible and where, 5-row / 6-row / large text modes, progress bars, drag-and-drop placement on a live preview
- Config export/import as JSON (includes the color palette)
- Firmware update: upload a
.binover the air
The clock gets the time over NTP and applies the timezone rules locally. By default it
asks pool.ntp.org, then time.nist.gov.
The Network page has a Time servers (NTP) card with a primary and a secondary field. Either accepts a hostname or an IP, so you can point the clock at a local time source (a router, a pfSense box, an internal NTP server) instead of the public pool. Leave the primary blank to fall back to the compiled default; leave the secondary blank to use no fallback server at all.
Test probes each configured server directly and reports whether it answered, with the UTC time it returned. That check uses its own throwaway socket, so it never disturbs the running clock. Saving the settings reapplies the timezone and forces a resync, which can take a few seconds on a slow server.
Both fields are included in config export/import.
The Weather Clock (style 14) shows current conditions next to the time: an animated icon (sun, clouds, rain, snow, storm...), the temperature, today's high/low, humidity and sunrise/sunset times. When enabled it also joins the Cycle All rotation as an extra screen.
Setup (web interface, Clock page, Weather Clock style):
- Tick Enable weather updates.
- Type your city into Find your location and press Search - it fills in the coordinates (the lookup runs in your browser; the device only stores latitude and longitude). You can also enter coordinates manually.
- Pick Celsius or Fahrenheit. Save.
Data comes from Open-Meteo (no account or API key needed), fetched every 10 minutes. The optional API key field is only for Open-Meteo commercial subscriptions. Icon, effect and temperature colors are editable in the style's Colors card like any other clock.
On the web interface's Display page you can run an ambient screensaver instead of
the clock: a Space Invaders battle, a Pac-Man chase, a starfield, an aquarium with
fish, bubbles and kelp, or a burning room where a very calm dog insists everything is
fine. An optional small clock stays in the corner. Press Start
now to keep the effect on until you stop it, or enable the schedule to have it come
on automatically during set hours (e.g. 20:00-23:00). GET /api/mode/ambient /
/api/mode/auto do the same from automations.
The Custom animation ambient effect plays animations you upload to the device from the board's flash storage (about 23 MB free on the Waveshare board, shared with the Lua effects). The UI reports the current upload budget, including space needed for a temporary file.
In the desktop companion, save pixelclock.local (or your clock's IP) on
Connection, then open Animations. Refresh storage, select a GIF, choose
crop/pad/stretch and its anchor, and create a preview. Automatic frame skipping
fits the clip to available space while preserving its duration. Upload the result
and use Play to try it. To keep it as the ambient effect, select it on the
clock's Display page and save. GIF input is limited to 8MiB; trim large clips first.
Alternatively, convert a GIF with the command-line tool:
pip install pillow
python tools/gif2pca.py my.gif # writes my.pca
python tools/gif2pca.py my.gif --preview check.gif # eyeball the result first
python tools/gif2pca.py my.gif --upload http://pixelclock.local # convert + uploadThe converter fits the GIF to the 128x64 panel (--fit crop|pad|stretch, with
--anchor start|center|end choosing which edge survives a crop - use --anchor end
to keep a caption at the bottom), quantizes all frames to one 16-color palette and
packs them into a compact .pca file (4KiB of pixel data per frame, 1.5MiB max, up to 360
frames; use --frame-skip 2 for long GIFs).
Upload either with --upload, with the file picker on the Display page (select the
ambient effect "Custom animation" to see it), or with curl:
curl -F "anim=@my.pca" "http://pixelclock.local/api/anim/upload"Then pick the animation in the dropdown, Save, and Start now. Uploaded
animations survive reboots and normal firmware-only OTA updates; replacing or
erasing the filesystem removes them. Manage them with
GET /api/anim/list and GET /api/anim/delete?name=<name>.
Select Custom rotation (style 9) in Clock settings. Enable the desired styles, move them with Up/Down, and set each duration from 5 to 3600 seconds, then save. At least one non-weather style must remain enabled. Rotation resumes from the first available style after another display mode interrupts it. Settings are included in configuration export/import.
Expand Diagnostics under Device status in the web portal for firmware,
flash/storage capacity, heap usage, reset reason, time/weather state and animation
errors. Download diagnostics saves the same information as JSON, without WiFi
credentials. It is also available at GET /api/diagnostics.
With the companion app running on your PC, the display switches to live hardware stats (CPU/GPU temps and loads, RAM, disks, fans, network throughput; up to 20 metrics) and returns to the clock when the PC goes offline.
Companion app v4 (Windows + Linux) lives in
PC-Companion-App-v4/: a tray app with a
web-style config window, live device preview, drag-and-drop layout editor and
sensor picker.
- Windows: download and run
pc_stats_monitor_v4.exe, no Python needed. Install LibreHardwareMonitor and run it as Administrator for temperature/fan/power sensors (on 0.9.5+ enable Options > Remote Web Server > Run). - Linux:
cd PC-Companion-App-v4/linux-companion, thenpython3 -m pip install -r requirements.txtandpython3 pc_stats_monitor_v4_linux.py.
The release includes the Windows companion alongside the firmware. Follow the Windows companion instructions to run from source or rebuild it.
Metrics are sent as JSON over local UDP (port 4210), at the companion's configured update interval. Both companions default to 3 seconds. CPU usage depends on the host, enabled sensors and update interval.
Do not want the stats screen? Untick Send PC stats to the display on the Connection page. The companion stops reading sensors and stops sending packets, so the device falls back to its clock or scheduled ambient screen. Combined with the audio visualizer below, that gives a display that is either the equalizer while music plays or the clock the rest of the time.
With the companion app streaming your PC's sound, the display becomes a 32-band spectrum analyzer: smooth bars with a green/yellow/red gradient (colors editable), falling peak dots, and an optional small clock in the corner.
Choose Visualizer style in the device web UI's Display -> Audio visualizer card, then Save settings:
- Classic EQ: the original 32 bars, editable colors and falling peak dots (default).
- Neon Mirror: segmented cyan and magenta bars pulse outward from a central horizon, with bright peak markers for a synthwave look.
- Phosphor Waterfall: a scrolling spectrum history in green, mint and amber, inspired by vintage computer displays. Bass is on the left, treble on the right; new sound enters at the top and fades downward.
- Purple LED Stage: a curved concert light wall. Each column follows its own band, bass opens the wave and treble adds pale pink highlights.
- Starfield Overdrive: flight through stars whose trails stretch on every bass onset.
- Oscilloscope: the live waveform on a lab-scope graticule, with a phosphor trail behind it. The trace is trigger-aligned on the PC so it stands still instead of sliding, and it takes its colors from the same three editable slots as Classic EQ (grid from the low color, trace from mid, peaks from the top one).
Classic EQ and the Oscilloscope each have their own color pickers, and the Colors and options card shows the set that belongs to the selected style; the others use fixed palettes. All of them support the small clock and the same companion audio stream. Style selection survives restarts and is included in settings export/import; older settings keep Classic EQ by default on a fresh device.
Selecting the Oscilloscope also reveals its own options, all of which default to the look above:
- Graticule: its own color, or switched off for a bare trace (default on).
- Trace colors: the trace itself and the color it fades to at full deflection (default yellow fading to red).
- Flat trace color: drops that fade so the trace is one color (default off).
- Fill to centre line: a solid silhouette instead of a bare line (default off).
- Phosphor trail: 0 to 4 ghost traces behind the live one. 0 is a single sharp line, 4 smears the most (default 3).
- Vertical gain: 50 to 200 percent trace height. Above 100 the loud parts flatten against the top and bottom edges, like a scope driven too hard (default 100).
Restore oscilloscope defaults on save puts all of those back, colors included, without touching any other setting.
The Oscilloscope needs the waveform that the companion app from this release sends alongside the spectrum. An older companion streams the spectrum only, and the device then says so on screen instead of drawing a trace; the other styles keep working with either version.
Turning Show small clock off also hides the fixed guide lines in Neon Mirror and Phosphor Waterfall. Waterfall then uses the full display height, and the Oscilloscope re-centers its graticule on the full panel.
Setup:
- On the PC: tick Audio visualizer stream on the companion's Connection
page and save. The Windows executable build bundles the audio dependencies;
when running from source, install
soundcardandnumpyif needed (python -m pip install soundcard numpy). It captures whatever the PC is playing (WASAPI loopback on Windows, PulseAudio monitor on Linux) - no cables, no microphone. - On the device: Audio visualizer page -> Start visualizer
(or
GET /api/mode/vizfrom an automation).
The visualizer stays on until you stop it; if the audio stream disappears for 10 seconds the display falls back to automatic display selection: PC stats while the companion is online, otherwise the scheduled ambient effect or clock. The visualizer returns automatically when the stream resumes.
Step 2 can be automatic. Under the stream checkbox, tick Start the visualizer when music plays and save. The companion then watches how loud the captured audio is and switches the display for you:
| Setting | Default | What it does |
|---|---|---|
| Start delay | 3 s | Sound must keep playing this long before the companion calls /api/mode/viz. Short sounds (Windows pops, chat notifications) never reach it. |
| Stop delay | 20 s | Quiet for this long calls /api/mode/auto, handing the display back to PC stats, ambient or the clock. |
| Sound threshold | -45 dB | Anything quieter counts as silence. Lower it (-55) if quiet music is missed, raise it (-35) if background sounds trigger it. |
Quiet gaps shorter than a second (between tracks, pauses in a song) do not restart the start delay, and the companion only releases the display if it was the one that switched it. The Connection page shows the current sound level in dB, so you can read it while music plays and set the threshold below it.
After a temporary network failure (for example, waking the PC), failed automatic
mode changes are retried until they succeed or a newer mode replaces them. The
stream keeps its last resolved device IP through temporary .local lookup failures.
If the clock restarts during playback, automatic mode restores the visualizer
after detecting its new uptime (checked every 10 seconds).
Open nickoscope.github.io/AnimatedPixelClock
in Chrome or Edge on a desktop, plug the board in over USB and press Install. It
writes the full image at 0x0, then hands your WiFi to the board over USB
(Improv Serial) in the same tab. The page also has a serial log viewer, useful
if the panel stays dark after a flash, and the Windows companion download.
The page carries one board, the Waveshare ESP32-S3-RGB-Matrix, because it is the one that has been flashed from there and seen to boot. Upstream runs its own flasher at pixelclock.stolaris.dev for the boards it supports.
Built with PlatformIO.
pio run -e matrix-waveshare-rgb -t uploadThe board needs opi_opi memory, not the qio_opi of WROOM-1 builds: with quad
flash set, the image uploads and then every boot dies in do_core_init right
after "Octal Flash Mode Enabled". platformio.ini has it right; the note is here
because the symptom looks like a dead board. Do not flash it with a WROOM
image for the same reason.
matrix-waveshare-rgb-bringup builds a standalone panel self-test
(bringup/hello_matrix.cpp), useful before the full firmware. The upstream
environments (matrix-s3-wroom, matrix-s3) still build.
Release packaging is described in docs/firmware/README.md.
The web flasher hands your network over right after installing. If you miss
that, the board opens an open access point named PixelClock-Setup; join it
and a captive portal (or 192.168.4.1) takes your WiFi credentials. Then the
panel shows its IP address, and the portal is at that address or at
http://pixelclock.local.
After the first flash, update over WiFi from the portal's Firmware Update section, or from the command line:
curl -F "firmware=@.pio/build/matrix-waveshare-rgb/firmware.bin" http://<device-ip>/updateFrom a release,
upload OTA_ONLY_firmware-v<version>-waveshare.bin. Do not upload the full
firmware-v<version>-waveshare.bin: it carries the bootloader and partition
table and belongs at 0x0 over USB. Downloads can be checked against
SHA256SUMS.txt.
Simple GET endpoints for home automation (Home Assistant, Node-RED, cron + curl). These controls do not save settings themselves. Mode/display overrides reset on reboot; brightness and style changes update the in-memory settings and can be persisted by a later settings save. No authentication, so keep the device on a trusted LAN.
| Endpoint | Description |
|---|---|
/api/status |
Current display/mode state as JSON |
/api/display/off / /api/display/on |
Blank / restore the panel |
/api/display/brightness?value=0-100 |
Set brightness (percent) |
/api/mode/clock / /api/mode/auto |
Force the clock / resume automatic mode |
/api/mode/ambient |
Force the ambient screensaver on now |
/api/mode/viz |
Force the audio spectrum visualizer (needs the companion streaming) |
/api/clock/style?id=<id> |
Switch the clock style; use an ID from the table above (13 is retired) |
/api/ntptest?server=<host> |
Probe an NTP server and report whether it answers |
/api/reboot |
Soft-restart (settings kept) |
curl http://pixelclock.local/api/display/off
curl "http://pixelclock.local/api/display/brightness?value=30"
curl "http://pixelclock.local/api/clock/style?id=8"Home Assistant example:
rest_command:
clock_display_off:
url: "http://pixelclock.local/api/display/off"
clock_display_on:
url: "http://pixelclock.local/api/display/on"Push a message banner onto the display from anything that can send an HTTP request. The banner appears over the active screen (including ambient effects and the visualizer), scrolls if the text is too long, and disappears on its own.
curl -X POST http://pixelclock.local/api/notify \
-H "Content-Type: application/json" \
-d '{"text":"Doorbell!","icon":"bell","color":"#FFAA00","duration":8000}'| Field | Required | Description |
|---|---|---|
text |
yes | Message, UTF-8, up to 200 bytes: 200 Latin letters or 100 Cyrillic ones |
color |
no | Banner color as #RRGGBB (default white) |
icon |
no | One of bell, mail, alert, heart, check, cross, info, home, music, star |
duration |
no | Display time in ms, 1000-60000 (default 5000) |
position |
no | top or bottom (default: the position set in the web interface) |
GET /api/notify/dismiss clears the banner early. A new POST replaces the current
banner. The feature can be disabled entirely on the web interface's Display page
(Notifications card), where the default banner position is also set.
Home Assistant example:
rest_command:
clock_notify:
url: "http://pixelclock.local/api/notify"
method: POST
content_type: "application/json"
payload: '{"text":"{{ message }}","icon":"{{ icon | default(''info'') }}","color":"{{ color | default(''#FFFFFF'') }}"}'This is a fork of Keralots/AnimatedPixelClock by Keralots: the clock styles, the ambient effects, the web portal, the PC companion and the audio visualizer come from there. Its sibling project for small OLED screens is SmallOLED-PCMonitor.
- ESP32-HUB75-MatrixPanel-DMA (matrix driver)
- Adafruit GFX, WiFiManager (tzapu), ArduinoJson, Improv-Serial
- Lua 5.4 for the effects
Licensed under the MIT License.
AnimatedPixelClock is an independent, non-commercial hobby project. It is not affiliated with, endorsed by, sponsored by or connected to Nintendo, The Tetris Company, Bandai Namco, Taito, Atari, Konami or any other rights holder.
The clock and ambient style names describe what each animation is styled after, so that you can tell the styles apart. Every sprite and effect in this firmware is drawn procedurally from the source in this repository, with user-configurable colors. No game artwork, sprite sheets, tile data, ROM data, fonts, sounds or music from any commercial game are copied, bundled or distributed here, and the firmware does not emulate or reproduce any of those games.
All product names, game titles, logos and brands referenced in this project are the property of their respective owners. They are used here only to describe the visual style of an animation, and their use does not imply any endorsement, sponsorship or affiliation.