A firmware launcher and hardware toolbox for the M5Stack Tab5 (ESP32-P4). LOADOUT lists firmware images from the microSD and boots them, updates itself over the air from GitHub Releases, and exposes the Tab5 hardware (camera, IMU, GPIO, I2C, UART, power, audio) as a set of on-device tools.
It is a fork of the official M5Tab5-UserDemo, rebuilt around a launcher home screen, a real-widget UI (LVGL 9 + smooth_ui_toolkit), a HAL abstraction with a desktop simulator, and an OTA-capable partition layout.
Architecture (HAL abstraction with a desktop simulator):
graph TD
UI["App / UI — LVGL 9 + mooncake<br/>launcher + tools"] --> HAL["HAL interface<br/>GetHAL() · app/hal/hal.h"]
HAL --> ESP["HalEsp32<br/>(device)"]
HAL --> DESK["HalDesktop<br/>(SDL2 simulator)"]
ESP --> HW["Tab5 hardware<br/>camera · IMU · INA226 · RX8130 RTC<br/>WiFi via ESP32-C6 · microSD · HW JPEG"]
Boot, firmware launch and OTA (A/B slots with rollback, so a bad image never bricks the device):
flowchart TD
A["Power on"] --> B["Bootloader picks last valid app = LOADOUT"]
B --> C{PIN set?}
C -->|yes| LK["Lock screen"] --> D["LOADOUT home<br/>(marks itself valid)"]
C -->|no| D
D -->|"pick .bin from microSD"| F["Flash to free OTA slot"]
F --> G["Boot it — pending validation"]
G -->|reset| B
D -->|"OTA update over WiFi"| K{"latest.json newer?"}
K -->|yes| N["Download → write free slot → reboot → mark valid"] --> D
K -->|no| D
The easiest way is the browser-based flasher (Chrome or Edge on desktop, nothing to install):
https://0day1day.github.io/loadout-tab5/
Connect the Tab5 over USB-C, click "Connect & Flash LOADOUT", pick the serial port, and confirm. A full erase and install is performed.
Command-line alternative (esptool): download loadout-tab5-factory.bin from the
latest release and run:
esptool --chip esp32p4 write_flash 0x0 loadout-tab5-factory.binRelease assets:
loadout-tab5-factory.bin— full image for a clean flash at offset0x0.loadout-tab5.bin— app-only image, used by the on-device OTA updater.latest.json— OTA manifest; set its URL asota_urlinloadout.conf.
To build from source instead, see Building.
The Tab5 is a capable ESP32-P4 device (1280x720 touch, 32 MB PSRAM, camera, IMU, RTC, WiFi via an ESP32-C6 co-processor). LOADOUT turns it into:
- A firmware launcher: drop
.binfiles on the microSD, pick one, and it flashes and boots. A failed or alternate firmware rolls back to LOADOUT on the next reset, so the device is never bricked by a bad image. - A self-updating app: LOADOUT checks a GitHub Release manifest over HTTPS and updates itself (A/B OTA partitions with rollback).
- A hardware toolbox: quick access to the camera, motion sensor, GPIO, I2C scanner, UART monitor, power monitor, audio, and a file browser, all from one UI.
- M5Stack Tab5 — ESP32-P4 (RISC-V), 1280x720 MIPI-DSI touch panel, 32 MB PSRAM, 16 MB flash.
- WiFi is provided by an on-board ESP32-C6 co-processor over SDIO (ESP-Hosted).
- Display/panel/touch driven through the official
m5stack_tab5BSP.
A second target (M5Cardputer, ESP32-S3) is on the roadmap; the launcher core and panels are kept UI-agnostic to ease that port.
- Launcher / firmware flashing — lists
.binfiles from the microSD; boots a selected image to an OTA slot. Remembers the last flashed firmware. - OTA update — checks a
latest.jsonmanifest on a GitHub Release over HTTPS (with the certificate bundle), shows progress, writes to the free OTA slot, reboots, and marks the new image valid. Rollback to LOADOUT if an image is not confirmed. - File browser + text editor — navigate folders, copy/cut/paste/rename/delete, and edit
text files (
.txt/.cfg/.ini/.json/.log/.md/.csv/.conf) with the keyboard. - Camera — live preview from the SC2356 sensor (V4L2-style
esp_videopipeline). - IMU / Motion — crosshair + ball visualization driven by the accelerometer.
- GPIO test, I2C scanner (internal and external buses), UART monitor.
- Power monitor — bus voltage / current / power via INA226, plus CPU temperature.
- Music player — MP3 playback from the microSD with an animated equalizer.
- AI chatbot — OpenAI-compatible chat endpoint (DeepSeek/Qwen/OpenAI/Groq/Ollama), multi-turn, configured from the SD config file.
- Screen recorder — captures the framebuffer, encodes JPEG with the ESP32-P4 hardware
encoder, and writes a frame sequence to
/sd/rec/. Convert to GIF/MP4 on a PC withtools/frames2gif.sh. - WiFi AP + STA — hosts an access point and connects to a home network. A web dashboard (battery, voltage/current, brightness/volume sliders, reboot) is served on the AP.
- PIN lock screen — optional boot lock with an on-screen numeric keypad and physical keyboard support. See "Security".
- Themes — multiple runtime color themes (applied live, persisted in NVS), with an optional animated background.
- Physical keyboard — full navigation and text entry with the Tab5 keyboard.
- RTC clock — persistent real-time clock (RX8130), settable from the UI, optional NTP sync.
- UI: LVGL 9 with
mooncake(app framework) andsmooth_ui_toolkit(animated C++ widget wrappers / modal windows). FontAwesome icons are embedded. - HAL abstraction: all hardware access goes through
GetHAL()(seeapp/hal/hal.h).HalEsp32implements it on device;HalDesktopprovides a desktop stub. This allows the same app/UI code to run on hardware and in the simulator. - Desktop simulator (SDL2): build and iterate the UI on a Mac/Linux host without flashing.
- Partitions / OTA: two OTA slots (
ota_0+ota_1) with bootloader rollback. External firmwares are flashed to the free slot and boot pending-validation, so a reset returns to LOADOUT unless the image marks itself valid. - Configuration: a single
/sd/loadout.confon the microSD plus persisted NVS settings.
app/ UI-agnostic application (mooncake apps, launcher view, HAL interface)
apps/app_launcher/ launcher home + all tool windows (view.cpp)
hal/hal.h HAL interface (virtual methods implemented per platform)
assets/ embedded fonts / FontAwesome icons
platforms/
tab5/ ESP-IDF project for the device (HAL impl, BSP, partitions, CI target)
desktop/ SDL2 simulator (HAL stub)
tools/ helper scripts (frames2gif.sh, GIF capture, etc.)
.github/workflows/ CI: build firmware and publish a Release on tag
fetch_repos.py clones external dependencies into dependencies/
repos.json pinned dependency revisions
External libraries (LVGL, mooncake, smooth_ui_toolkit, mooncake_log) are not vendored; they
are cloned at pinned revisions into dependencies/:
python ./fetch_repos.pycd platforms/tab5
idf.py build
idf.py -p /dev/cu.usbmodemXXXX flashThe device project root is platforms/tab5. Target is esp32p4.
# deps: cmake + libsdl2 (Linux: sudo apt install build-essential cmake libsdl2-dev,
# macOS: brew install sdl2)
cmake -S . -B build_desktop
cmake --build build_desktop -j8
./build/desktop/app_desktop_buildTo exercise the PIN lock screen in the simulator, set LOADOUT_PIN:
LOADOUT_PIN=1234 ./build/desktop/app_desktop_buildContinuous integration (.github/workflows/loadout-firmware.yml) builds the firmware in the
espressif/idf:release-v5.4 container. It can be run manually (workflow_dispatch) for a
test build, and on pushing a vX.Y.Z tag it publishes a GitHub Release containing:
loadout-tab5.bin— the application image for OTA.latest.json— the manifest the device reads (version, download URL, notes).
To cut a release:
git tag v1.0.0
git push origin v1.0.0On the device, point OTA at the manifest by setting ota_url in /sd/loadout.conf to the
release's latest.json URL. The OTA tool then compares versions and updates over WiFi.
Created with defaults on first boot. Keys (edit and reboot):
| Key | Purpose |
|---|---|
wifi_pass |
Password for the LOADOUT access point (min 8 chars) |
wifi_sta_ssid / wifi_sta_pass |
Home WiFi for internet / OTA |
theme |
UI theme index |
tz |
POSIX timezone for NTP |
ota_url |
URL of the OTA latest.json manifest |
ai_url / ai_key / ai_model |
OpenAI-compatible chatbot endpoint |
pin |
Lock PIN (4-8 digits). Empty or pin=clear disables the lock |
The lock screen is a convenience lock, not strong security. The PIN is stored in plaintext
in NVS (internal flash), and optionally on the microSD via loadout.conf. To harden, enable
ESP-IDF flash encryption. If you are locked out, set pin=clear in /sd/loadout.conf on a
PC and reboot.
Based on the M5Stack M5Tab5-UserDemo and the following open-source projects:
- https://github.com/lvgl/lvgl
- https://github.com/Forairaaaaa/smooth_ui_toolkit
- https://github.com/Forairaaaaa/mooncake
- https://github.com/Forairaaaaa/mooncake_log
- https://www.heroui.com
- https://github.com/alexreinert/piVCCU/blob/master/kernel/rtc-rx8130.c
- https://components.espressif.com/components/espressif/esp_cam_sensor
- https://components.espressif.com/components/espressif/esp_ipa
- https://components.espressif.com/components/espressif/esp_sccb_intf
- https://components.espressif.com/components/espressif/esp_video
- https://components.espressif.com/components/espressif/esp_lvgl_port
- https://github.com/jarzebski/Arduino-INA226
- https://github.com/boschsensortec/BMI270_SensorAPI
MIT, inheriting the license of the upstream M5Tab5-UserDemo. See LICENSE.
