# AtlasCube **Repository Path**: rexlai/AtlasCube ## Basic Information - **Project Name**: AtlasCube - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-24 - **Last Updated**: 2026-09-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # AtlasCube *English | [Polski](README.pl.md)* ⚠️ Development has moved. This repository is an archive of the AtlasCube source up to v0.51.0. Current firmware, releases and the changelog now live at [atlascube.net](https://atlascube.net) β€” flash or update your device from [atlascube.net/flash](https://atlascube.net/flash), and see the [changelog](https://atlascube.net/changelog) for what's new. Issues and pull requests here are no longer monitored. A hobby project β€” internet radio and smart clock running on a generic dev board (for now) with ESP32-S3 (AtlasCube). Streams internet radio, shows a clock, manages reminders, and exposes a web UI for configuration. Everything runs on the device with no cloud dependency. 🌐 **[atlascube.net](https://atlascube.net)** β€” project home
➑️ Web UI demo Live, in-browser preview of the device web UI β€” click around without the hardware
⚑ Flash from browser Install prebuilt firmware over USB from a Chromium browser β€” no ESP-IDF, no esptool, no CLI
πŸ–ΌοΈ Wallpaper gallery Download ready-made AtlasCube wallpapers grouped by display resolution
πŸ”§ Build from source Different display or your own pin layout? Pick the variant and set every GPIO in main/include/defines.h, then build with one command
πŸ“± Android remote app Phone remote β€” playback, EQ, events, layout editor (beta)
--- ## Screenshots ### Device screens

Clock β€” dark

Clock β€” light

Radio β€” dark

Radio β€” light

Clock β€” dark

Clock β€” light

Radio β€” dark

Radio β€” light

Playlist β€” dark

Playlist β€” light

Settings β€” dark

Settings β€” light

Event β€” dark

Event β€” light

Equalizer

Bluetooth

Diagram with ILI9341

Diagram with CO5300

Diagram with SSD1322
### Web UI

Radio

Bluetooth

Layouts editor

Files manager

Display

MQTT

Screensavers

Theme

Dim & Wake

Tools

Editor

Playlist

Events

Equalizer
--- ## Features **Audio** - Internet radio streaming β€” MP3, AAC, FLAC (via [esp-adf](https://github.com/espressif/esp-adf)) - HLS live streams β€” plays segmented `.m3u8` playlists alongside plain MP3/AAC/FLAC streams; the MPEG-TS segments are demuxed on the fly (AAC or MP3 audio, per the stream) - Podcasts β€” plays a podcast episode as a **finite HTTP stream** (a distinct mode from endless radio: end-of-file is a clean stop, not a reconnect), with the episode title shown on screen, and **resumes mid-episode** via an HTTP `Range` request. The [Android app](https://github.com/marcinozog/AtlasCube-Remote/) is the catalog β€” it browses/searches feeds (iTunes search, Apple charts, or add-by-URL β€” all keyless), sends the episode's direct URL, and remembers the playback position; audio streams straight from the CDN to the device, exactly like a radio station - ICY metadata β€” station name and now-playing track shown on screen and in the web UI - 10-band parametric EQ + soft volume (custom DSP element, core 1) - Playlist β€” up to 512 stations, stored in SPIFFS - Station logos β€” an optional per-station image shown on the radio screen; find one through the Radio Browser lookup or upload your own, and the web UI converts it to the panel's RGB565 format and stores it on the SD card - Bluetooth audio β€” A2DP sink and HFP hands-free (external QCC5125 module, Bluetooth 5.1); supported codecs: LDAC, aptX HD, aptX LL, aptX, SBC, AAC - SD card music player β€” play MP3 / WAV / FLAC / AAC files straight from a microSD folder. Browse subfolders, queue with shuffle and repeat (none / all / one), pause/resume, and auto-advance β€” all from the `/sd-player.html` web page. A third audio source alongside radio and Bluetooth (one active at a time); shares the EQ and volume with the radio output - Hardware I2S source switching β€” a 74HC157D multiplexer routes either the ESP32-S3 or the QCC5125 I2S output to the DAC, selected by a single GPIO - Automatic retry on stream loss - Resume on boot β€” optionally replays the last station after a restart if the radio was playing when it powered off (opt-in, toggled in the Settings web UI) **UI** - LVGL-based GUI β€” supports ILI9341 320Γ—240 (SPI), ST7789V 320Γ—240 (SPI), ST7796U 480Γ—320 (SPI), ILI9488 480Γ—320 (SPI, 18-bit), CO5300 240Γ—296 round AMOLED (QSPI), and SSD1322 256Γ—64 mono OLED (SPI), switched via a single compile-time define - Screens: home hub (clock face + adaptive controls), radio, SD player, playlist, equalizer, settings, Bluetooth, events, WiFi AP - Home hub β€” the default screen: a clock face that adapts to the active source (radio / SD / BT), with a tap overlay to control playback and jump to the playlist / SD browser / BT / settings. It covers all sources from one screen; the per-source screens are optional (hide them from Settings β†’ Display) - Rotary encoder navigation (turn + press) - Touch β€” capacitive CST816D (CO5300 round AMOLED) or FT6336U (ST7796U 480Γ—320) on I2C, or resistive XPT2046 (SPI; shares the LCD bus or a dedicated SPI3, calibrated per UI profile); coexists with the rotary encoder, either input works at any time - Swipe gestures β€” horizontal swipes cycle the home ring (home ↔ bt ↔ radio ↔ sd ↔ mqtt, skipping hidden screens); swipe-up opens settings (home) or the source list (radioβ†’playlist, SDβ†’browser); detection runs through LVGL on the existing pointer indev, no per-chip glue - On-screen controls overlay β€” tap a screen to bring up the playback controls (play/stop, volΒ±, prev/next), auto-hides after a short timeout; the home hub's overlay adds source/playlist/sd/settings buttons - Audio VU meter β€” an optional radio-screen widget showing a real-time FFT spectrum computed from the live audio output; position it via the layout editor - Analog needle VU meter β€” two independently placed left/right channel meters, each just a thin frame and a pivoting needle drawn over the wallpaper, so the meter faces can be painted right into the background image (e.g. a vintage amplifier photo); position and size each meter separately in the layout editor - Stereo bar VU meter β€” two independently placed left/right channel bars fed by the same per-channel level as the needle meter (no FFT), each growing vertically or horizontally with an optional peak-hold marker; position and size each bar separately in the layout editor - Weather widget β€” optional current temperature and conditions on the home screen, with day/night condition icons; choose keyless Open-Meteo or OpenWeatherMap (API key required), then configure coordinates, Celsius/Fahrenheit and refresh interval in Settings β†’ Weather - Configurable layout β€” position and style widgets in the visual web editor (stored as JSON); optional theme-aware background plates and adjustable opacity keep labels, the weather widget and station information readable over wallpapers - Screen background β€” choose a gradient, a solid color, an **SD wallpaper image** (panel-sized RGB565 `.bin`), or fetch a JPEG directly from an internet URL. The layout editor can also browse the [online wallpaper gallery](https://atlascube.net/wallpapers/), automatically show images matching the configured display resolution, convert and install one to the SD card, and let you choose its filename. Built-in presets, on-boot/daily refresh, saving to SD and 0–80% dimming are available from the Settings web UI - Custom boot splash logo β€” drop a panel-sized RGB565 `.bin` on the SD card to replace the built-in logo (auto-fit; falls back to the built-in logo if missing) - Optional per-source screens in the home ring β€” show or hide the Radio, SD player and Bluetooth screens from the Settings web UI (the home hub stays, so the ring is never empty) - 180Β° flip & colour inversion β€” rotate the whole screen upside-down (for inverted mounting) or invert panel colours (fixes batches where e.g. yellow shows as blue); both toggle from the Settings web UI and apply live, no restart - Touch orientation β€” swap X/Y and mirror either touch axis independently of the display flip, for panels whose touch layer is mounted differently than the matrix (taps landing mirrored or on the wrong axis); also live from the Settings web UI - Screensavers β€” kick in after a configurable idle timeout; choose from clock hands, starfield, fireworks, plasma, Conway's Game of Life, blank (AMOLED-friendly "off"), **Dim** (just lowers the backlight, keeps the current screen), **Dashboard**, or **Photo frame** (see below) **Dashboard screensaver** - A user-configurable ambient display that polls any JSON HTTP/HTTPS endpoint and renders a single value - Configurable from the Settings web UI: **title**, **URL**, **JSON path** (dot/bracket notation, e.g. `rates[0].mid` or `main.temp`), **suffix** (e.g. ` PLN`, `Β°C`), and **poll interval** (β‰₯ 5 s) - HTTPS supported out of the box via the ESP-IDF certificate bundle β€” works with public APIs that need no auth - Defaults ship with the NBP USD/PLN exchange rate; swap the fields to read pretty much anything serving JSON (weather, crypto, home automation, GitHub stats, …) - Polling runs in a dedicated FreeRTOS task only while the screensaver is active β€” no background traffic when another screen is shown **Photo-frame screensaver** - Turns the device into a digital photo frame β€” cycles through images stored on a microSD card - Slideshow images are pre-converted to LVGL's panel-sized RGB565 binary format (by the Android app or the [`scripts/img2lvgl.py`](scripts/img2lvgl.py) helper) and dropped on the card. The photo-frame path deliberately does not decode JPEG/PNG slides (the JPEG decoder is used only for internet wallpapers), so even large slides cost no extra decode-time RAM - Configurable from the Settings web UI or the Android app: **source folder**, **order** (sequential / random), **seconds per slide**, **reveal effect** and **reveal speed** - The slow SD load is turned into the transition: each new image **develops over the previous one** with a configurable reveal β€” **top-down**, **wipe**, **dissolve**, **interlaced** (retro low-res β†’ sharp), or **random per slide** - Renders into two full-screen PSRAM buffers and repaints only the area that changed each tick, so it stays light even while the radio streams - Settings apply live β€” changing the effect/order/timing updates the running slideshow within a slide, no need to leave the screensaver - Manage slides from anywhere: browse / upload / rename / delete them via the web **SD file manager** (Settings β†’ Tools) or the Android app, which also converts and uploads phone photos in one step - Requires a microSD card wired to the build's SDMMC pins (1-bit mode) **Events & reminders** - Birthdays, namedays, anniversaries, plain reminders, voice notifications and playback schedules - Recurring: daily / weekly / monthly / yearly - On-screen fullscreen notification + buzzer melody at trigger time; melodies are programmable in firmware (web-based editor planned) - Playback schedules β€” start a radio station or an SD file/folder at a configured time, with an optional stop time. Start and stop are linked internally but shown and edited as one item; one-time, daily and weekly ranges can cross midnight - Voice notification type β€” plays a spoken clip from the microSD card at trigger time, briefly interrupting and then restoring the radio/Bluetooth source. The Android app synthesizes the speech on the phone (TTS) and uploads it; each clip is a single file named after a readable slug of the event title (e.g. `voice/wake-up-call-a3f9c1.wav`), so the card stays browsable in the file manager. Both the web and app editors can preview the clip - CRUD via web UI **Connectivity** - WiFi STA with AP fallback (first-boot setup via 192.168.4.1) - HTTP server + WebSocket for real-time state sync - mDNS β€” reachable at `.local` in STA mode (default `atlascube-xxxx` derived from the MAC, editable in Settings β†’ WiFi); advertises an `_http._tcp` service with a TXT record carrying the `.local` name for discovery clients (e.g. the Android app's NsdManager) - NTP time sync with configurable timezone - Web UI served from SPIFFS (no internet required after flash) - MQTT client β€” remote control of the radio (play/stop/volume/station) plus up to 6 configurable widgets (toggle / slider / label) on a dedicated on-device screen, driving any external MQTT device (Tasmota, zigbee2mqtt, Home Assistant, …); see [MQTT](#mqtt) below - OTA firmware update β€” upload a new app image straight from the web UI (Settings β†’ System); it streams into the inactive slot, validates, and reboots, with bootloader rollback if the new image won't start. A backup/export button downloads the currently running firmware first. The web UI and your settings live in separate flash partitions, so an OTA app update never overwrites them. See [OTA updates](#ota-updates) below - Automatic updates β€” at boot the device checks for a newer release of its variant and shows an on-screen **Update / Later** prompt; firmware installs through the same dual-slot OTA, and a web UI left behind by an app-only update is refreshed in place with one press (no reboot). The prompt can be turned off in Settings β†’ System. See [Automatic updates](#automatic-updates) below **Storage** - Optional microSD card over SDMMC (1-bit mode), wired to the build's SDMMC pins - Web **SD file manager** (Settings β†’ Tools) β€” browse folders, create directories, upload, rename, and delete files straight from the browser (LVGL `.bin` images preview inline); the Android app can push files too - Web **SPIFFS ⇄ SD backup/restore** (Settings β†’ Tools) β€” a separate dual-pane manager that copies files between the device's SPIFFS and the SD card: back up configs / web UI to the card and restore them later. Client-side, copy-only - Web **Settings & stations backup** (Settings β†’ System) β€” one-click *Export settings* downloads every user file on the config partition (settings, theme, events, MQTT, layout, station list) as a single `.json`, and *Import settings* restores it. No SD card needed; layout-independent, so a backup survives a partition change (e.g. before a full USB flash that erases user data). Wi-Fi/MQTT passwords are stored separately and are not included - Backs the photo-frame slides, voice-notification clips, local music for the SD player, station logos, plus the optional screen wallpaper and custom boot splash logo **Android app** *(beta)* - Remote control for playback, station switching, and volume - Modelled after the YoRadio Remote interface, extended with AtlasCube-specific features: event management, equalizer, layout editor, podcast browser (RSS + iTunes/Apple discovery, resume) - Separate repo with its own README: [AtlasCube-Remote](https://github.com/marcinozog/AtlasCube-Remote/) --- ## Hardware | Component | Details | |---|---| | MCU | ESP32-S3, 240 MHz, dual-core | | Board | Atlas Hub (custom) | | Flash | 16 MB | | PSRAM | OctoSPI, 80 MHz | | Display | ILI9341 320Γ—240 (SPI), ST7796U 480Γ—320 (SPI), ILI9488 480Γ—320 (SPI, 18-bit), CO5300 240Γ—296 AMOLED (QSPI), or SSD1322 256Γ—64 mono OLED (SPI) β€” selected at compile time | | Touch | CST816D or FT6336U capacitive controller (I2C) β€” gestures detected by LVGL on the standard pointer indev | | Input | Rotary encoder with push button + capacitive touch (swipes + tap-to-control overlay) | | I2S mux | 74HC157D β€” hardware switch between ESP32-S3 and QCC5125 I2S outputs; controlled via GPIO | | Audio out | I2S DAC / amplifier (fed from 74HC157D output) | | Bluetooth | QCC5125 external module, Bluetooth 5.1, A2DP + HFP | | Microphone | Built-in, used for HFP hands-free | | Buzzer | LEDC PWM tone generator | --- ## Quick start β€” flash prebuilt firmware You don't need ESP-IDF or a toolchain to put AtlasCube on the device. Tagged releases publish ready-to-flash images, and there's a one-click installer in the browser. ### Easiest: flash from your browser Open **[atlascube.net/flash](https://atlascube.net/flash/)** in Chrome / Edge / Opera / Brave, pick your display variant, plug the device in over USB, click Install. Zero CLI, zero install. (Firefox and Safari don't support WebSerial.) > First flash: hold the **BOOT** button while plugging USB, then release β€” puts ESP32-S3 into download mode. Required because the running firmware drives native USB-CDC and ignores the auto-reset. ### Or: flash from CLI with esptool **1. Pick your display variant:** | File | Display | Touch | |---|---|---| | `AtlasCube-ili9341-ft6336u.bin` | ILI9341 320Γ—240 (SPI) | FT6336U | | `AtlasCube-st7796-ft6336u.bin` | ST7796U 480Γ—320 (SPI) | FT6336U | | `AtlasCube-ili9488-ft6336u.bin` | ILI9488 480Γ—320 (SPI, 18-bit) | FT6336U | | `AtlasCube-co5300-cst816d.bin` | CO5300 240Γ—296 (QSPI AMOLED) | CST816D | | `AtlasCube-ssd1322.bin` | SSD1322 256Γ—64 (mono OLED, SPI) | β€” (encoder) | | `AtlasCube-ili9341-xpt2046.bin` | ILI9341 320Γ—240 (SPI) | XPT2046 (resistive) β€” experimental | | `AtlasCube-st7789v-xpt2046.bin` | ST7789V 320Γ—240 (SPI) | XPT2046 (resistive) β€” experimental | | `AtlasCube-st7796-xpt2046.bin` | ST7796U 480Γ—320 (SPI) | XPT2046 (resistive) β€” experimental | | `AtlasCube-ili9488-xpt2046.bin` | ILI9488 480Γ—320 (SPI, 18-bit) | XPT2046 (resistive) β€” experimental | The `-xpt2046` variants are not yet hardware-verified β€” calibration may need tuning. **2. Download** the matching `.bin` from the [latest Release](https://github.com/marcinozog/AtlasCube/releases/latest). **3. Flash** with [esptool](https://github.com/espressif/esptool) (one-time `pip install esptool`): ```bash esptool.py --chip esp32s3 -p write_flash 0x0 AtlasCube-.bin ``` Substitute `` with your serial port (`/dev/ttyUSB0`, `COM3`, …). **4. First boot:** the device starts an AP named `AtlasCube-XXXXXX`. Connect, open `192.168.4.1` and configure Wi-Fi. That's it β€” no ESP-IDF, no ESP-ADF, no patches. The rest of this README describes the dev build, needed only when you want to modify the firmware. --- ## Build **Requirements** - [ESP-IDF v5.5.4](https://github.com/espressif/esp-idf) - [ESP-ADF v2.8](https://github.com/espressif/esp-adf) **One-command build & flash (recommended)** > Full step-by-step guides: [docs/build-windows.md](docs/build-windows.md) (ESP-IDF Installation Manager) and [docs/build-linux.md](docs/build-linux.md) (`install.sh` + `export.sh`). `scripts/build-flash.py` is the all-in-one user script. Set your hardware variant in [`main/include/defines.h`](main/include/defines.h), install [ESP-IDF v5.5.4](https://github.com/espressif/esp-idf) (the official installer is the only manual step on Windows), open the ESP-IDF environment, then from the repo root run: ```bash python scripts/build-flash.py -p COM5 ``` On the **first run** it sets up ESP-ADF for you (clone + patches β€” no separate step), then compresses the web UI, builds, and flashes a connected board, asking how much of the device to overwrite: | Scope (`--scope`) | Flashes | Keeps | |---|---|---| | Everything / factory (`all`) | bootloader + partition table + app + `www` + `config` | β€” (works on a blank chip; resets settings to defaults) | | Firmware only (`fw`) | app slot (OTA-style update) | web UI + settings | | Firmware + Web UI (`ui`) | app + `www` partition | settings | | Build only (`build`) | nothing β€” compile + `web/*.gz` | everything | | Erase all (`erase`) | wipes the whole flash (app + web UI + settings + NVS) | β€” | On a fresh or erased chip use **Everything / factory** (`all`) β€” it's the only scope that also writes the bootloader and partition table, so the chip can boot. `Firmware only` / `Firmware + Web UI` only update the app / web UI and need a bootloader already present (a blank chip would fail with `invalid header`). `all` flashes the full web UI and resets settings to defaults; the device then boots into AP mode for Wi-Fi setup at `192.168.4.1`. The flash layout splits the old storage partition into `www` (the editable web UI) and `config` (user settings JSON), so reflashing code or the UI never wipes your settings β€” only a factory flash reseeds defaults. Pass `--scope all|fw|ui|build|erase` to skip the prompt, and `--monitor` to open the serial monitor afterwards. Run `build-flash.py` with no `--scope` to get an interactive menu; one entry, **Update from git**, runs `git pull --ff-only` to fetch the latest repo (this script included), then asks you to re-run it. Back up your `defines.h` first β€” it is tracked by git, so a pull can clash with your local HW/pin edits. To tweak the web UI without flashing at all, edit files live in the browser (the on-device file editor, or the built-in setup page upload) β€” they write straight to the `www` partition over HTTP. > Switching the HW variant in `defines.h`? `build-flash.py` detects a stale `sdkconfig` (the old display/touch defines linger because changed `sdkconfig.defaults` aren't re-applied) and offers to delete it for a clean build. Pass `--clean` to force it without the prompt. **What the first-run setup does** The setup logic lives in `scripts/env_setup.py` and runs on the first build (shared by both `build-flash.py` and `ci/build.py`). It is idempotent β€” safe to re-run; `build-flash.py --setup` re-runs just the patches. It does the following: - Clones ESP-ADF v2.8 into `./esp-adf` if `ADF_PATH` is not already set. - Initializes ESP-ADF submodules `components/esp-adf-libs` and `components/esp-sr` (pre-compiled libraries not pulled by a plain clone). - Copies the AtlasCube board definition into `esp-adf/components/audio_board/esp32_s3_atlascube/`. - Patches `Kconfig.projbuild`, `CMakeLists.txt` and `component.mk` in `esp-adf/components/audio_board/` to register the board. - Applies the FreeRTOS patch (`idf_v5.5_freertos.patch`) on ESP-IDF β€” required for `xTaskCreateRestrictedPinnedToCore`, without which the MP3 decoder task fails at runtime (`E AUDIO_THREAD: Not found right xTaskCreateRestrictedPinnedToCore`). `sdkconfig.defaults` already contains `CONFIG_ESP32_S3_ATLASCUBE_BOARD=y`.
Manual steps β€” what the setup automates, for reference / debugging If you'd rather do it by hand (or are debugging the setup): 1. **ESP-ADF submodules:** ```bash git -C $ADF_PATH submodule update --init components/esp-adf-libs components/esp-sr ``` 2. **Board sources** β€” copy or symlink: ```bat mklink /D %ADF_PATH%\components\audio_board\esp32_s3_atlascube \components\audio_board\esp32_s3_atlascube ``` 3. **Register in `esp-adf/components/audio_board/Kconfig.projbuild`** (inside the `AUDIO_BOARD` choice): ```kconfig config ESP32_S3_ATLASCUBE_BOARD bool "ESP32-S3-AtlasCube" ``` 4. **Register in `esp-adf/components/audio_board/CMakeLists.txt`** (before `register_component()`): ```cmake if (CONFIG_ESP32_S3_ATLASCUBE_BOARD) message(STATUS "Current board name is " CONFIG_ESP32_S3_ATLASCUBE_BOARD) list(APPEND COMPONENT_ADD_INCLUDEDIRS ./esp32_s3_atlascube) set(COMPONENT_SRCS ./esp32_s3_atlascube/board.c ./esp32_s3_atlascube/board_pins_config.c ) endif() ``` 5. **Register in `esp-adf/components/audio_board/component.mk`** (legacy GNU Make build): ```makefile ifdef CONFIG_ESP32_S3_ATLASCUBE_BOARD COMPONENT_ADD_INCLUDEDIRS += ./esp32_s3_atlascube COMPONENT_SRCDIRS += ./esp32_s3_atlascube endif ``` 6. **FreeRTOS patch on ESP-IDF:** ```bash git -C $IDF_PATH apply --ignore-whitespace $ADF_PATH/idf_patches/idf_v5.5_freertos.patch ```
**Pick the hardware variant** The active variant lives in [`main/include/defines.h`](main/include/defines.h) β€” three independent `#define` groups: `DISPLAY_*`, `UI_PROFILE_*`, `TOUCH_*`. Uncomment exactly one in each group; `build-flash.py` reads them as-is. (`ci/build.py ` overwrites them for you β€” handy in CI.) After switching the variant by hand, run `idf.py fullclean` so `sdkconfig` is regenerated from the new combination (`build-flash.py --clean` does this for you). **Pin configuration** Every GPIO has a compile-time default in [`main/include/defines.h`](main/include/defines.h) (there is no `menuconfig`/Kconfig for pins). You can also remap pins **at runtime β€” no rebuild** β€” from the built-in setup page; `defines.h` then just provides the defaults. Edit `defines.h` (and rebuild) when you want to bake new defaults into a binary. | Peripheral | Defines | Notes | |---|---|---| | Display | `LCD_PIN_*` (SPI) / `DISPLAY_PIN_*` (QSPI) | inside the per-driver `#if CONFIG_DISPLAY_*` block | | Touch (I2C) | `CTP_SCL`, `CTP_SDA`, `CTP_INT`, `CTP_RST` | CST816D / FT6336U; `-1` = unused (`TOUCH_NONE` skips it) | | Touch (SPI) | `TP_CLK`, `TP_MOSI`, `TP_MISO`, `TP_CS`, `TP_IRQ` | XPT2046 only; `TP_CLK`/`TP_MOSI` = `-1` shares the LCD bus | | SD card | `SD_PIN_CLK`, `SD_PIN_CMD`, `SD_PIN_D0`, `SD_PIN_CD` | SDMMC 1-bit; CMD/D0 need ~10k pull-ups | | I2S DAC | `I2S_DATA`, `I2S_BCK`, `I2S_LCK` | also read by ESP-ADF (via the board's `get_i2s_pins`) | | Bluetooth | `BT_MODULE_TX_PIN`, `BT_MODULE_RX_PIN`, `BT_MOULE_PIN` | QCC5125 UART | | Encoder | `ENC_CLK_PIN`, `ENC_DT_PIN`, `ENC_BTN_PIN` | turn + press | | Buzzer | `BUZZER_PIN` | `-1` to disable | The display pins are grouped per driver, so set your variant first (above) β€” you only edit the block that matches the active `DISPLAY_*`. **Runtime pin setup (no rebuild)** Open `http:///setup` (or `192.168.4.1/setup` in AP mode; also linked from Settings β†’ Tools). The page lets you remap display / touch / SD / I2S / encoder / buzzer / Bluetooth GPIOs and stores them in NVS, overriding the `defines.h` defaults β€” so one binary fits boards with different wiring. It flags reserved (26–37), strapping (0/3/45/46) and duplicate pins, and blocks saving on hard conflicts. The allowed-GPIO list is colour-coded **in use (red) / free (green)** so a spare pin is obvious at a glance, and the whole map can be **exported/imported as a JSON file** (the file records the driver and firmware version, so an import warns if it was made for a different build). **Power-cycle** the device after saving (a soft restart does not reliably remap GPIO pads). "Reset pins to defaults" clears the overrides. The display *driver* itself is still fixed at build time β€” pins are configurable, the driver is not. **Build and flash manually** Once the variant and patches are in place (`build-flash.py --setup` does just the setup), the usual ESP-IDF flow works: ```bash idf.py build idf.py flash ``` > **Editing the board files while iterating with plain `idf.py`** (e.g. the > VS Code ESP-IDF extension's build button): `idf.py` builds the copy inside your > ESP-ADF clone, so repo edits to `components/audio_board/esp32_s3_atlascube/` > won't take effect until you re-run `build-flash.py --setup`. To keep > edits live, replace the ADF copy with a junction (no admin needed): > > ```powershell > $dest = "$env:ADF_PATH\components\audio_board\esp32_s3_atlascube" > Remove-Item -Recurse -Force $dest > New-Item -ItemType Junction -Path $dest -Target "\components\audio_board\esp32_s3_atlascube" > ``` > > The setup detects an existing symlink/junction and leaves it in place. **Single merged image (CI / release)** `ci/build.py` is the release entry point that CI runs: it selects the variant from a CLI argument (overwriting `defines.h`), runs the same first-run setup, builds, and produces a merged, distributable `build/AtlasCube-.bin`. Most users don't need it β€” `build-flash.py` above covers building & flashing your own board. ```bash python ci/build.py co5300 # or ili9341 / st7796 / ili9488 / ssd1322 python ci/build.py # interactive variant menu ``` The merged `.bin` combines bootloader, partition table, app, and both SPIFFS images (`www` + `config`) into one file flashable from offset `0x0` with `esptool` or a web flasher. To build it by hand: ```bash python spiffs_image/tools/compress_web.py idf.py build idf.py merge-bin -o AtlasCube.bin ``` Flash with: ```bash esptool.py write_flash 0x0 AtlasCube.bin ``` ### Custom fonts Fonts live in [`components/ui/fonts/`](components/ui/fonts/) as LVGL C arrays. The sizes are not a standard β€” they are chosen per panel. The `_NN` in a name is the `--size` (line height in px); the large `_72/_80/_96` files are **digit-only** (`--range 0x30-0x3A` plus one icon), while the `_NN_eu` files carry every Latin-script European alphabet: ASCII plus Latin-1 Supplement (`0x00A0-0x00FF`), Latin Extended-A (`0x0100-0x017F`) and the Romanian comma-below forms (`0x0218-0x021B`). Regenerate them all with [`scripts/gen_fonts.ps1`](scripts/gen_fonts.ps1). To add a new font (e.g. a larger `montserrat_120`): 1. **Generate** the `.c` with [lv_font_conv](https://lvgl.io/tools/fontconverter) (the exact `Opts:` used are in each file's header). For a digit-only clock font: ```bash lv_font_conv --font Montserrat-Medium.ttf --range 0x30-0x3A \ --font FontAwesome5-Solid+Brands+Regular.woff --range 0xF0F3 \ --size 120 --bpp 4 --format lvgl --no-compress -o lv_font_montserrat_120.c ``` Drop the file into [`components/ui/fonts/`](components/ui/fonts/). 2. **Compile** it β€” add the filename to the source list in [`components/ui/CMakeLists.txt`](components/ui/CMakeLists.txt). 3. **Declare** it β€” add `LV_FONT_DECLARE(lv_font_montserrat_120);` in [`ui_fonts.h`](components/ui/fonts/ui_fonts.h). 4. **Register** it β€” append `{ "montserrat_120", &lv_font_montserrat_120 },` to the table in [`ui_fonts.c`](components/ui/fonts/ui_fonts.c). The id then shows up automatically in the web UI font dropdowns and is serialized into the UI profile. Note: a glyph is shorter than the nominal size (β‰ˆ72 % of `--size` for digits), so to get a digit `X` px tall pick `--size β‰ˆ X / 0.72`. See [`docs/layout_editor.md`](docs/layout_editor.md#font-registry) for how fonts map to screen fields. --- ## Web UI Available at the device IP or `.local` (STA mode), or `192.168.4.1` (AP mode). | Page | Path | |---|---| | Radio / now playing | `/` | | SD music player | `/sd-player.html` | | Settings | `/settings.html` | | Playlist | `/playlist.html` | | Events | `/events.html` | | Equalizer | `/eq.html` | | Layout editor | `/layout.html` | | SPIFFS file editor | `/spiffs-editor.html` | | SD card file editor | `/sd-editor.html` | | File manager (SPIFFS / SD) | `/manager.html` | | MQTT widgets | `/mqtt.html` | WebSocket endpoint: `ws:///ws` β€” pushes state changes (volume, track, radio state) in real time. In **Layout editor β†’ Wallpaper**, use **Online gallery** to browse wallpapers matching the active display resolution. **Preview** opens the source image, while **Install** downloads it through the browser, converts it to the panel-sized RGB565 `.bin` format and uploads it to `/wallpapers/x` on the SD card (wallpapers are filed by panel resolution, like layout presets). Before upload you can change the destination filename; `.bin` is added automatically. An internet connection is required only while browsing and installing from the gallery. The running firmware version (from `git describe`) is shown in the web UI header, on the Wi-Fi setup page, and β€” together with the device IP β€” on the **boot splash** for a few seconds in STA mode (toggleable in Settings β†’ Display). A quick way to confirm what was flashed and how to reach the device. --- ## MQTT The device runs an MQTT client that connects to a local broker (e.g. Mosquitto) on the LAN. Configure it from **Settings β†’ MQTT** in the web UI: host, port, username/password, client ID, base topic. After saving, the client reconnects on the fly β€” no reboot needed. - **Compile-time switch**: `CONFIG_MQTT_ENABLE` (menuconfig β†’ *MQTT configuration*). Default `y`; set `n` to drop the component entirely from the firmware. - **Connection**: plain TCP (LAN-only, no TLS), QoS 0, automatic reconnect (handled by `esp-mqtt`). - **Will / online status**: the device publishes `online` (retained) to `/status` on connect, and the broker delivers `offline` (LWT, retained) on unexpected disconnect. - **Payload style**: plain text on hierarchical topics (Tasmota/HA-style) β€” easy to use from `mosquitto_pub` and to wire into Home Assistant via `command_topic`/`state_topic` in YAML. ### Topic map All radio topics use the prefix `/` (default: `atlascube/`). The MQTT `client_id` is a separate broker-level identifier and does not appear in topic names. It has to be unique on the broker β€” two clients connecting with the same id kick each other off in an endless loop β€” so the default is `atlascube-`; leaving the field blank regenerates it. | Topic suffix | Direction | Payload | Notes | |---|---|---|---| | `cmd/play` | subscribe | any | resumes the active source (radio / SD player / Bluetooth) | | `cmd/stop` | subscribe | any | stops the active source | | `cmd/next` / `cmd/prev` | subscribe | any | next/previous on the active source; radio wraps around the playlist | | `cmd/source` | subscribe | `radio` \| `sd` \| `bt` | switches the audio source | | `cmd/volume` | subscribe | `0`–`100` | clamped | | `cmd/station` | subscribe | playlist index | 0-based | | `state/playing` | publish (retain) | `playing` \| `stopped` \| `buffering` \| `error` | | | `state/volume` | publish (retain) | `0`–`100` | | | `state/station_index` | publish (retain) | playlist index | | | `state/station` | publish (retain) | station name | from playlist entry | | `state/title` | publish (retain) | ICY title | "" when stopped | | `status` | publish (retain) + LWT | `online` \| `offline` | LWT delivers `offline` if the device drops | ### Widgets screen A dedicated on-device screen (the MQTT entry in the home ring) hosts **up to 6 user-defined widgets** in a grid. Each slot is configured independently from `/mqtt.html` (linked from Settings β†’ MQTT). Set a slot's *Type* to `None` to disable it. **Widget types** - **Toggle** β€” publishes `ON`/`OFF` on the cmd topic when tapped; visual state follows the state topic (so the UI stays in sync if the device is toggled from HA, a physical button, an automation, …). - **Slider** β€” configurable `min` / `max` / `step`; publishes the numeric value on the cmd topic and tracks the state topic. - **Label** β€” read-only; displays the latest value from the state topic, with an optional `unit` suffix (`Β°C`, `%`, …). **Common fields** - **Title** β€” short string shown above the widget. - **Command topic** β€” published on user interaction (toggle/slider). Example (Tasmota): `cmnd/livingroom/POWER`. Example (zigbee2mqtt accepts plain text on `/set`): `zigbee2mqtt//set`. - **State topic** β€” subscribed on connect/reconnect; drives the widget's displayed value. - **JSON path** β€” when non-empty, extracts a single field from JSON payloads (works on both directions): - *Incoming*: e.g. `state` pulls `"ON"` out of zigbee2mqtt's `{"state":"ON", ...}`. - *Outgoing*: the cmd publish is wrapped as `{"": }` instead of raw text β€” handy for devices that expect JSON (zigbee2mqtt `{"brightness":128}`). - Empty path = plain-text mode in both directions. - Plain-text payload parser accepts `ON`/`OFF`/`on`/`off`/`true`/`false`/`1`/`0` for booleans, bare numbers for sliders. ### Examples Watch everything the device publishes: ```bash mosquitto_sub -h 192.168.1.10 -v -t 'atlascube/#' ``` Control the radio: ```bash mosquitto_pub -h 192.168.1.10 -t atlascube/cmd/play mosquitto_pub -h 192.168.1.10 -t atlascube/cmd/volume -m 30 mosquitto_pub -h 192.168.1.10 -t atlascube/cmd/station -m 2 ``` Minimal Home Assistant YAML: ```yaml mqtt: switch: - name: AtlasCube Radio command_topic: atlascube/cmd/play payload_off: stopped # use cmd/stop for off; or split into two switches state_topic: atlascube/state/playing payload_on: playing number: - name: AtlasCube Volume command_topic: atlascube/cmd/volume state_topic: atlascube/state/volume min: 0 max: 100 sensor: - name: AtlasCube Title state_topic: atlascube/state/title ``` > HA MQTT Discovery (auto-registration) is not implemented yet β€” entities are declared manually as above. **File editor** `/spiffs-editor.html` is an in-browser editor for the web UI files β€” HTML/CSS/JS and other text assets. It lists files from the `www` partition, lets you edit them with syntax highlighting, and saves back over HTTP without reflashing (HTML/CSS/JS are re-gzipped on the device). Useful for tweaking layouts or the web UI on a deployed device. The editor can also open the separate `config` partition (the settings JSON) for direct edits β€” handy for debugging β€” though normally those files are managed through their own screens (Settings, Events, MQTT, …). --- ## OTA updates Update the firmware over Wi-Fi from **Settings β†’ System** β€” no USB cable, no esptool. The page shows the running version, takes a firmware image, streams it to the device, and reboots into it. Progress is mirrored on the device screen. **What it touches:** | Partition | OTA touches it? | Notes | |---|---|---| | app (`ota_0` / `ota_1`) | βœ… writes the **inactive** slot, then switches boot | the only thing OTA writes | | `www` (web UI) | ❌ untouched | update separately (file editor / setup page / full reflash) | | `config` (settings) | ❌ untouched | your settings survive | | bootloader + partition table | ❌ untouched | can't be changed over OTA | | NVS | ❌ untouched | pin map (GPIO config) survives | **How it flows:** `0xE9` magic check β†’ `esp_ota_begin` erases the inactive slot β†’ stream + `esp_ota_write` β†’ `esp_ota_end` validates the checksum β†’ `esp_ota_set_boot_partition` β†’ reboot (bootloader rolls back if the new image won't start). The two app partitions live in [`partitions16MB.csv`](partitions16MB.csv); if no inactive slot is present the endpoint returns `501`. **Which file:** upload the **app-only** image β€” either `AtlasCube--ota.bin` from the [latest Release](https://github.com/marcinozog/AtlasCube/releases/latest) (also linked from [atlascube.net/flash](https://atlascube.net/flash)) or your own `build/atlascube.bin` (~2.3 MB). *Not* the merged `AtlasCube-.bin`, which also contains the bootloader, partition table and the `www`/`config` partitions and is meant for a full `0x0` USB flash. Make sure the image matches your display variant; flashing a different variant's binary will break the UI. **Adopting the layout:** switching an existing 16 MB device to the OTA partition layout is a one-time full USB reflash (a partition-table change can't go through OTA itself). After that, every further update is web-only. **Safety:** - The device stops playback during the write to free RAM and avoid flash/SPI contention. - **Backup first:** the *Download image* button (`GET /api/ota/backup`) downloads the running app image as `atlascube-.bin` β€” the same format *Install image* accepts, so you can upload it again to roll back manually. Firmware only: settings, web UI files and WiFi credentials are not included. When a firmware update also ships new web UI, OTA leaves the `www` partition as-is β€” the device notices the mismatch and offers to fix it with one press via the on-device **WEB UI OUTDATED** prompt (see [Automatic updates](#automatic-updates) below). Manual fallbacks still work: the **setup page** (`/setup`) shows a *web UI out of date* banner with a one-click link to the matching `AtlasCube-www.zip` from the latest release β€” extract it and upload the files there (include `www_version.txt` to clear the warning) β€” or edit/upload via the in-browser file editor (`/spiffs-editor.html`), or do a full `0x0` reflash. --- ## Automatic updates The device also keeps itself current β€” no browser needed. On boot (Wi-Fi STA), it asks atlascube.net whether a newer release exists for its hardware variant (the request carries the variant, the running firmware version and an anonymous device id β€” no account, no personal data). Depending on the answer: - **Newer firmware available** β†’ a **NEW FIRMWARE** prompt with **Update / Later** buttons appears on the device screen. Confirming downloads the app-only image for the running variant over HTTPS and installs it through the same dual-slot OTA flow as above (settings and rollback included), then reboots. - **Firmware current, web UI stale** β†’ an app-only update never rewrites the `www` partition, so the web UI can lag behind. The device detects this and shows a **WEB UI OUTDATED** prompt instead; confirming pulls the fresh web files from the release package straight onto the `www` partition β€” in place, live, no reboot. - **Later** dismisses the prompt until the next boot. The on-screen prompt can be disabled in **Settings β†’ System** (the boot version check itself still runs). Devices flashed before the updater shipped need one manual update (USB or the web OTA above) to pick it up; from then on they keep themselves up to date. --- ## Project docs Architecture and design notes in [`docs/`](docs/): - [`audio_pipeline.md`](docs/audio_pipeline.md) β€” streaming pipeline, DSP, TCP tuning, task affinity - [`events.md`](docs/events.md) β€” reminder/event system design - [`layout_editor.md`](docs/layout_editor.md) β€” UI layout customization - [`wallpapers.md`](docs/wallpapers.md) β€” putting a wallpaper on a screen: online gallery, internet slots, SD files - [`navigation.md`](docs/navigation.md) β€” screen map: the home ring, inputs (encoder/touch), how to edit it - [`display_drivers.md`](docs/display_drivers.md) β€” display driver gotchas (QSPI AMOLED even-boundary, shared SPI mutex, LVGL buffer vs internal DRAM budget) - [`ws_protocol.md`](docs/ws_protocol.md) β€” control protocol shared by the web UI, the Android app and the hardware remote (WS commands, state broadcast, REST) --- ## Roadmap - **Enclosure** β€” 3D-printed case currently in design; firmware is developed and tested on the bare development board - **Web melody editor** β€” in-browser tool for composing custom buzzer notification tunes --- ## License MIT ### Third-party fonts The UI ships glyphs from the following fonts, converted to LVGL bitmap format (`components/ui/fonts/*.c`): - **Montserrat** β€” Β© The Montserrat Project Authors, [SIL OFL 1.1](https://openfontlicense.org) - **Font Awesome 5 Free** β€” Β© Fonticons, Inc., [SIL OFL 1.1](https://openfontlicense.org) (fonts) / [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) (icons) - **Weather Icons** β€” Β© [Erik Flowers](https://erikflowers.github.io/weather-icons/), [SIL OFL 1.1](https://openfontlicense.org)