ScryBar is an open-source ESP32-S3 desk companion. One 3.49" touchscreen, six swipeable views, a word clock that composes real sentences in thirteen languages (from Italian and Latin to Klingon, 1337 Speak, and Bellazio), actual grammar, not uppercase tiles, plus RSS feeds, a Wikipedia viewer, a Now Playing strip with cover art, a live transit departure board, and a LAN web config UI.
Why "ScryBar"? Part scry (gazing into a surface to see things you shouldn't), part scribe (it writes sentences, not just numbers), part bar (look at it, it's a bar). A 640×172 strip that tells time in Klingon, fetches weather from an API you could just open yourself, scrolls headlines you already read on your phone, pulls random Wikipedia facts nobody asked for, and shows live train departures you could just check on your phone. On your desk. Between the coffee mug and the cable spaghetti. If that's not scrying, nothing is.
| ScryBar Default | Cyberpunk 2077 | Tokyo Transit | Minimal Brutalist Mono |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Yes, that is cyberpunk in Latin. If you want neon UI saying Hora septima est, ScryBar will not judge.
- Hardware
- Views
- Word Clock Languages
- How It Works
- Quick Start
- Secrets
- Feature Toggles
- Web Config (LAN)
- Serial Command Reference
- Screenshot Workflow
- Security
- Archive
- Acknowledgments
- Open Source Spirit
- License
| Component | Spec | Role |
|---|---|---|
| MCU | ESP32-S3, 240 MHz, dual-core | The brain. 16 MB flash, OPI PSRAM in octal mode, because LVGL needs room to think. |
| Board | Waveshare ESP32-S3-Touch-LCD-3.49 | The whole stack in one unit: display, touch controller, IMU, power management, battery connector. |
| Display | AXS15231B, 3.49", 640×172 | The face. Horizontal strip format. LV_COLOR_16_SWAP=1 because it expects RGB565 big-endian and is not open to discussion about this. |
| Touch | AXS15231B integrated | Single-point touch. Carefully filtered for ghost frames and sentinel coordinates. |
| IMU | QMI8658 6-axis | Accelerometer + gyroscope. Shake detection, tilt sensing. The bar knows when you're angry. |
| Power | USB-C + optional LiPo | Charging and battery fallback managed via TCA9554 GPIO expander. Always re-asserted at boot. |
The physical profile: a horizontal bar that sits flat on your desk. Wide enough to host six views of mischief. Narrow enough that it stops pretending to be a monitor and commits to being furniture that has opinions.
Six views, navigated by swipe.
INFO ◄─► HOME ◄─► AUX (RSS) ◄─► WIKI ◄─► NOW PLAYING ◄─► TRANSIT
HOME — Word clock in natural sentence form (13 languages), weather icon, temperature, humidity. Theme-driven typography with auto-fit sizing. Themes switchable from the web UI without reflashing.
AUX — RSS feed rotation with up to 5 configurable sources. SKIP/NXT/QR controls. Every headline gets a live QR code, because sometimes you want to read the full article on a real screen, and that's fine.
WIKI — Wikipedia stream: Featured Article, On This Day, and Random Article. Language independently selectable (8 real languages) from the system language via web UI. Same SKIP/NXT/QR controls as AUX. A bottomless pit of trivia that you didn't need but now can't stop reading.
NOW PLAYING — Cover art on the left, metadata on the right, playback progress and transport controls. The firmware UI is now live on-device, and the production path is a small macOS companion that discovers the bar over Bonjour, posts metadata to /api/now-playing, and by default reads the system-wide Mac Now Playing session through MediaRemote.framework. That gives ScryBar one practical feed for Music, Spotify, TIDAL, podcasts, and whatever else macOS itself is already surfacing.
TRANSIT — Live departure board powered by Transitous (free global GTFS data, no API key). Search any station worldwide from the web UI. Colored line badges with adaptive font, real-time delay indicators, arrival times, platform info. Handles trains, metro, trams, buses, and coaches across 20+ countries. Tap to toggle origin display; destination filter via web UI.
INFO — System diagnostics: Wi-Fi status, RSSI, IP, MAC, battery level, power source, firmware build tag, NTP sync status. QR code pointing to the web config UI. The nervous system, exposed.
Physical buttons:
PWR(center): short press toggles screensaver; hold1.5sfor power-off (hard-off on battery via TCA9554, soft-off on USB); hold1.5sagain to wake back toHOME.BOOT(left): single click jumps toHOME.RST(right): hardware reset. The nuclear option.
Auto-idle screensaver: 2h on both USB and battery.
13 languages, all selectable from the web UI without reflashing. Setting persists to NVS.
Creative & Constructed:
| Code | Language | Example (3:15) |
|---|---|---|
bellazio |
Bellazio | Raga, le tre e un quarto. For real. |
val |
Valley Girl | It's like quarter past three, totally |
l33t |
1337 Speak | 1T'5 QU4R73R P457 7HR33 |
sha |
Shakespearean English | Verily, 'tis quarter past three |
eo |
Esperanto | estas kvarono post la tri |
la |
Latina | hora tertia et quadrans |
tlh |
tlhIngan Hol (Klingon) | wej rep wa'maH vagh tup |
Modern Languages:
| Code | Language | Example (3:15) |
|---|---|---|
en |
English | it's quarter past three |
it |
Italiano (default) | sono le tre e un quarto |
es |
Español | son las tres y cuarto |
fr |
Français | il est trois heures et quart |
de |
Deutsch | es ist viertel nach drei |
pt |
Português | são três e quinze |
Adding a language means writing 4 functions (word clock, weather short, weather label, date format) plus a UiStrings block and updating the dispatchers. The framework does the rest. If you can conjugate verbs, you can add a language.
At boot: assert SYS_EN via TCA9554, cycle Wi-Fi SSIDs (or use preferred SSID), fall back to setup AP if no known network, sync NTP, render HOME.
Serial [SUMMARY] every 30s: build tag, Wi-Fi, NTP, UI page, weather state. Read it like a flight data recorder.
Touch passes through anti-ghost filtering (AXS15231B produces spurious frames at idle; the controller has opinions about how often it wants to be touched).
Prerequisites: arduino-cli, esp32 board package by Espressif, libraries listed in firmware_readme.md.
arduino-cli compile --clean \
--build-path /tmp/arduino-build-scrybar \
--fqbn esp32:esp32:esp32s3:UploadSpeed=921600,USBMode=hwcdc,CDCOnBoot=cdc,CPUFreq=240,FlashMode=qio,FlashSize=16M,PartitionScheme=custom,PSRAM=opi \
.arduino-cli upload -p <PORT> \
--fqbn esp32:esp32:esp32s3:UploadSpeed=921600,USBMode=hwcdc,CDCOnBoot=cdc,CPUFreq=240,FlashMode=qio,FlashSize=16M,PartitionScheme=custom,PSRAM=opi \
--input-dir /tmp/arduino-build-scrybar \
.This repo ships a checked-in partitions.csv and uses PartitionScheme=custom.
If upload hangs on Connecting..., enter boot mode: hold BOOT, press and release RST, release BOOT. This is not a bug. It is a handshake.
Open Serial Monitor at 115200 baud. You will see the boot banner, chip diagnostics, and (if TEST_WIFI=1) a connection attempt cycling through every configured SSID in sequence.
secrets.h is local-only and git-ignored. It holds Wi-Fi credentials and API keys.
cp secrets.h.example secrets.h
# fill in your credentialssecrets.h.example is committed and contains placeholders only (<WIFI_SSID>, <API_KEY>, etc.). Never put credentials in config.h or any versioned file. The .gitignore handles this by design, not accident.
Enable or disable subsystems in config.h. Nothing is compiled in unless explicitly toggled on.
| Toggle | What it activates |
|---|---|
TEST_SERIAL_INFO |
Chip, heap, flash diagnostics at boot |
TEST_BACKLIGHT |
Display backlight PWM test |
TEST_I2C_SCAN |
I2C bus scan, prints found addresses |
TEST_DISPLAY |
Display init via AXS15231B (Arduino_GFX) |
TEST_IMU |
QMI8658 accelerometer + gyroscope |
TEST_WIFI |
STA connection with multi-SSID retry |
TEST_NTP |
NTP sync, prints local_time=... |
TEST_BATTERY |
Battery monitoring via ADC (voltage, charge level, power source) |
TEST_TOUCH |
Required for swipe navigation. Boot log shows [SKIP] TEST_TOUCH=0 if disabled. |
DISPLAY_FLIP_180 |
180° rotation (USB-C left, speaker top). Default 1. |
WEB_CONFIG_ENABLED |
LAN web config UI on port 8080 when Wi-Fi is connected. |
When Wi-Fi is connected and WEB_CONFIG_ENABLED=1, ScryBar starts a lightweight HTTP server.
http://<DEVICE_IP>:8080
| Endpoint | Method | Does |
|---|---|---|
GET / |
Config UI (Tron-grid themed, responsive, reduced-motion fallback) | |
GET /api/config |
Current config as JSON | |
GET /api/wifi/scan |
Scan nearby 2.4 GHz Wi-Fi networks (bounded timeout, safe in AP setup mode) | |
GET /api/wifi/setup-qr.svg |
SVG QR for setup URL (http://192.168.4.1:8080 in AP mode) |
|
POST /api/config |
JSON body | Update config fields |
POST /config |
Form body | Update config via form UI |
POST /reload |
Force refresh weather and RSS feeds |
Config persists to NVS. Configurable: Wi-Fi preferred SSID, Wi-Fi Direct mode, new Wi-Fi provisioning (scan + password), weather city/lat/lon, logo URL, up to 5 RSS feeds, system language, Wikipedia language, and UI theme.
If no known network is reachable, setup AP starts (ScryBar-Setup-XXXX, 2.4 GHz). Join it and open http://192.168.4.1:8080 to scan and provision a new network. The INFO panel shows a QR code pointing to the config URL; scan it with your phone and you're in.
Commands sent over Serial at 115200 baud. Case-insensitive.
Navigation:
| Command | Effect |
|---|---|
VIEW |
Toggle HOME ↔ AUX |
VIEWFIRST |
Jump to first main view (HOME, excludes INFO) |
VIEWLAST |
Jump to last main view |
VIEW0 / VIEWINFO |
Force INFO page |
VIEW1 / VIEWHOME |
Force HOME page |
VIEW2 / VIEWAUX / VIEWRSS |
Force AUX/RSS page |
VIEW3 / VIEWWIKI |
Force WIKI page |
VIEW4 / VIEWNOW / VIEWNP |
Force NOW PLAYING page |
VIEW5 / VIEWTRANSIT / TRANSIT |
Force TRANSIT page |
Configuration:
| Command | Effect |
|---|---|
LANG |
Print current language code |
LANG <code> |
Set language (e.g., LANG tlh for Klingon) |
THEME |
Print current theme ID |
THEME <id> |
Set UI theme |
QRON / QROFF / QRTOGGLE |
Show / hide / toggle QR codes on feed views |
Diagnostics:
| Command | Effect |
|---|---|
SNAP / SCREENSHOT |
Capture framebuffer as raw RGB565 over serial (workflow below) |
BATSTAT |
Print battery voltage, level, power source |
RSSSTAT / WIKISTAT |
Print RSS / Wikipedia feed status |
RSSDIAG |
Detailed RSS diagnostic (feed URLs, status, error counts) |
RELOAD |
Force refresh weather + RSS + Wiki feeds |
WEBCFG |
Print web config server status + URL |
WIFIDIRECT |
Print Wi-Fi Direct mode / AP status |
WIFIDIRECT off|auto|on |
Set Wi-Fi Direct mode and persist to NVS |
Power & Screensaver:
| Command | Effect |
|---|---|
SAVERON / SAVEROFF |
Force screensaver on / off |
SAVERSTAT |
Print screensaver state + active idle target |
PWROFF |
Soft power-off (recoverable via power button) |
PWROFFHARD |
Hard power-off. Requires hardware power cycle. Handle with care. |
A [SUMMARY] block is emitted automatically every 30 seconds: build, Wi-Fi, NTP, UI, and weather state. Read it like a flight data recorder.
Capture live framebuffer snapshots over serial. Requires ffmpeg for the RGB565 → PNG conversion.
# Capture whatever is currently on screen
python3 tools/capture_snapshot.py --port <PORT> --out-dir screenshots
# Capture a specific view
python3 tools/capture_snapshot.py --port <PORT> --pre-cmd VIEWTRANSIT --out-dir screenshotsThe --pre-cmd flag sends a serial command before taking the snapshot, useful for switching views without touching the device. Add --pre-wait and --pre-gap to tune boot / settle timing.
Wire format is rgb565be. Resolution is always 640×172.
secrets.h is git-ignored and never committed. secrets.h.example is committed with placeholders only.
- Never commit
secrets.h. The.gitignorehas your back, but paranoia is a feature. - If a secret ever lands in git history: rotate it immediately, then clean history.
config.hand all versioned docs must remain credential-free. Always.
The archive/ansi/ directory contains the former ANSI/BBS art viewer: 27 embedded files from Blocktronics and Sixteen Colors, a full ANSI parser with SAUCE support, CGA palette, and CP437 rendering. Removed in r183 because the replay value of ten art files on a desk bar is approximately one afternoon. The code is fully intact with restoration instructions in archive/ansi/README.md if you disagree.
The ANSI parser was built with invaluable reference from icy_tools by Mike Krueger, the gold standard for ANSI art tooling. If you work with ANSI art on any platform, start there.
If you fork ScryBar, make it yours:
- swap feeds and weather locations,
- redesign views or add new ones,
- add a fourteenth language and send a PR,
- publish your variant and share improvements back.
Small screen. Wide horizon.
MIT. Use it, fork it, modify it, put it on a desk somewhere and tell people it's art (it is). Keep the copyright notice. No warranty. No liability. No hard feelings.
Built with Arduino, LVGL, too many filter constants, and the unwavering belief that a word clock in Klingon on an ESP32 is objectively better than anything else on your desk.







