Cardputer Hub is an extensible firmware platform for the M5Stack Cardputer-Adv.
The project is designed as a small personal device platform built around reusable Services and independent Mini Apps.
The long-term goal is to support functionality such as:
- Bluetooth host control
- multiple paired devices
- Wi-Fi connectivity
- weather information
- VPS monitoring
- media controls
- RGB status indication
- microSD-backed file storage
- Telegram integration
- remote control through a Web UI
- additional Mini Apps and hardware extensions
The project is intentionally not designed as a single-purpose MacBook remote.
The primary dependency direction is:
Mini Apps
↓
Services
↓
Connectivity
↓
Hardware Adapters
The System Core provides shared infrastructure such as:
Application lifecycle
Launcher
App Registry
Navigation
Input routing
Action Bus
Configuration interfaces
Record and file-storage primitives
Logging
Capabilities
WeatherApp
↓
WeatherService
↓
WiFiService
↓
ESP32 Wi-Fi
Mini Apps should remain thin.
Reusable logic, state, integrations, and background behavior belong in Services.
Project documentation is the source of truth for architecture and engineering decisions.
Read:
docs/ARCHITECTURE.md— system architecture, Services, Mini Apps, connectivity, host management, remote control, and planned development phasesdocs/ENGINEERING.md— testing, CI/CD, build system, versioning, releases, and development requirementsdocs/UI_REQUIREMENTS.md— delivered UI behavior and the remaining visual, motion, sound, display-power and input requirementsdocs/plans/README.md— implementation-plan status indexdocs/manuals/— installation, supported features, device controls, and user-facing proceduresAGENTS.md— instructions for Codex and other coding agents working in this repository
Architectural changes should update the relevant documentation in the same pull request.
src/
├── core/
├── connectivity/
├── services/
├── apps/
├── hardware/
└── main.cpp
companion/macos/
protocol/companion/
test/
docs/
├── ARCHITECTURE.md
├── ENGINEERING.md
├── UI_REQUIREMENTS.md
└── plans/
.github/
└── workflows/
scripts/
The apps, connectivity, and services directories contain the built-in
system UI, connectivity foundations, and host/configuration/battery/audio/companion
Services. companion/macos holds the optional macOS menu-bar Cardputer Companion.app.
protocol/companion holds the shared wire contract, its fingerprint generator
and binary fixtures; firmware and Companion are built from one commit and
refuse a peer built from another protocol definition.
The MAC STATUS Mini App shows live Mac system metrics, charging time and four
detail pages (CPU and top apps, power, network, memory and disk) while the
Companion is connected; polling runs only while the app is open.
AI combines STATUS and USAGE, switched with Left/Right, and remembers the last
page within a firmware session. USAGE shows discovered Codex/Cursor/Claude quotas
from the Companion and publishes a remaining-quota gauge on Unit Puzzle while
that page or its details are visible. Provider collection
is automatic on the Mac; firmware receives only normalized usage numbers.
STATUS shows whether Codex, Claude Code and Cursor desktop agents are
working, need you, or are done, from lifecycle hooks that Companion installs
on request; only application states cross BLE. Status polling continues on
USAGE, where a fresh wait/error adds ! beside STATUS. Puzzle follows the
visible page. Escape returns from usage details to USAGE, then closes AI.
SERVICES HEALTH shows the public status pages of GitHub, Anthropic, OpenAI and Cursor
while it is open. The Cardputer fetches them over HTTPS when Wi-Fi is connected,
and through the Companion otherwise. A page that gets worse plays a fault cue.
NFC is a personal inventory for boxes and bags: a writable NTAG213/215/216
sticker carries only a random inventory ID, and the container's name and item
list live as a JSON record on the Cardputer's microSD card. NfcApp renders
InventoryService, which registers blank stickers (writes and verifies the ID,
then saves the record) and shows a tapped container's list offline;
NfcService owns the optional M5Stack Unit NFC (ST25R3916) behind INfcReader
and withdraws NFC_READER if the unit disappears. The Companion's Inventory
window edits each container's name and free-text description (including
Cyrillic) over the live BLE session;
the Cardputer validates and commits every revision-checked write.
The locked development environment uses:
- Python 3.12.14;
- uv 0.12.13;
- ESP-IDF 5.5.5 with its recommended compiler, CMake, and Ninja tools;
- PlatformIO Core 6.1.19 only for native host tests and static analysis;
- clang-format 23.1.0;
- a C++17 host compiler for native tests and C++17 project-owned firmware components;
- exact managed-component and Git-submodule dependency locks.
On macOS, install Git and the host compiler with Xcode Command Line Tools:
xcode-select --installOn Ubuntu or Debian Linux, install the native build prerequisites:
sudo apt-get update
sudo apt-get install --yes build-essential git curl wget flex bison gperf \
python3 python3-pip python3-venv ccache libffi-dev libssl-dev dfu-util \
libusb-1.0-0Install the repository's exact uv version using its versioned installer, then let uv install Python and the locked tools. Review downloaded installation scripts before running them when required by your environment's security policy.
curl -LsSf https://astral.sh/uv/0.12.13/install.sh | sh
uv python install 3.12.14
bash scripts/install_esp_idf.sh \
"$HOME/.espressif/frameworks/esp-idf-v5.5.5"
source "$HOME/.espressif/frameworks/esp-idf-v5.5.5/export.sh"
make setupThe locked virtual environment is local to the checkout; these commands do not replace the macOS system Python. Verify the resolved versions with:
uv --version
uv run --frozen python --version
uv run --frozen pio --version
uv run --frozen clang-format --version
idf.py --versionDo not install project-specific ESP32 or M5Stack libraries globally. The
ESP-IDF component manager resolves the exact production graph from
main/idf_component.yml and dependencies.lock; Git submodules retain the
exact pinned hardware-support sources. The short build/check/upload/monitor
commands activate ESP-IDF automatically. Source its export.sh before calling
make setup, make configure, or ESP-IDF commands directly.
Follow the maintained
docs/manuals/installing-firmware.md
guide for prerequisites, USB installation, verification, and troubleshooting.
To keep Cardputer Hub installed beside Codex Microputer ADV, Bruce, or another
application in CRUB's shared extra slot, use the separate
Cardputer Firmware Manager.
It owns the shared crub layout, stages all or selected local builds and GitHub
Release images on microSD, and keeps application data partitions intact during
routine updates.
For a local multiboot build, start CRUB's usbsd mode and validate/stage the
Hub image through the sibling manager repository:
cd ../cardputer-firmware-manager
python3 -m firmware_manager doctor
python3 -m firmware_manager local --app hub --build --sd /Volumes/CARDPUTERThe manager calls scripts/build_firmware.sh, which activates the pinned
ESP-IDF 5.5.5 installation from the documented setup path, or a repository-local
build-tools/ installation when present, without inheriting another
application's ESP-IDF environment. Nonstandard installations may set
CARDPUTER_HUB_IDF_PATH and, when needed, CARDPUTER_HUB_IDF_TOOLS_PATH.
For local manager builds, the splash version includes the local build date and
time as VERSION+YYYYMMDD-HHMM; release builds retain their assigned semantic
version unchanged.
After safely ejecting the card and exiting usbsd, run sd so CRUB remounts
the card and reloads its aliases. Then run uphub and require both app: ok
and flash complete before running go. Hub shares CRUB's extra slot with
the other applications, so uphub replaces whichever one was installed. To
install a published build instead, use:
python3 -m firmware_manager release --app hub --sd /Volumes/CARDPUTERDo not copy or flash an unvalidated raw image directly.
If the USB Serial/JTAG console does not enumerate after a CRUB launch,
follow the install guide's
gofast diagnostic boot procedure.
Run make or make help for the short command list. No Node.js or npm is needed.
make build
make firmware-size
make check
make flash SD=/Volumes/CARDPUTER
make stage SD=/Volumes/CARDPUTER
make doctor SD=/Volumes/CARDPUTERmake build, make check, make firmware-size, make monitor, and
make flash-usb UPLOAD_PORT=/dev/cu.usbmodem... activate the installed pinned
ESP-IDF automatically through scripts/build_firmware.sh. Initial dependency
installation is still required (see Local Setup).
Host tests and analysis keep their PlatformIO packages in .cache/platformio
to avoid conflicts with the tool versions used by other firmware repositories.
make flash builds and stages Hub on the mounted FAT32 card through the sibling
cardputer-firmware-manager; make stage stages the existing image. Both run
the manager's doctor before and after staging. Safely eject the card, leave
CRUB usbsd, then run sd, uphub, and go on the Cardputer. USB flashing
uses Hub's existing upload command and writes only CRUB's shared extra slot.
SD defaults to /Volumes/CARDPUTER; FIRMWARE_MANAGER_DIR overrides the
manager checkout path and WORKSPACE overrides the parent of the firmware
repositories. make -n flash shows the commands without staging an image.
Companion commands also run from this repository root:
make companion-build
make companion-check
make companion-run
make companion-installInstallation defaults to ~/Applications/Cardputer Companion.app; override
with COMPANION_INSTALL_DIR=/Applications if that directory is writable.
Quit a running Companion before launching a rebuilt or installed version.
Installation does not launch the app or enable Start at Login.
make firmware-size inspects that completed production build without creating
a second firmware variant. It prints ESP-IDF's application/partition summary
and per-component flash contributions, and retains the same reports as
build/firmware-size.txt and build/firmware-size-components.txt.
The production application and matching partition-table images are written to
build/cardputer_hub.bin and
build/partition_table/partition-table.bin. The version-controlled flash layout
keeps the framework's default NVS separate from the dedicated hub_config NVS
partition reserved for authoritative configuration records. On startup, the
firmware enters through native ESP-IDF, initializes M5Unified directly, writes
structured informational records for the product name, version, commit, and
build type to serial, briefly renders the product name/version, then opens a
device-centric, application-neutral Home. Its fixed status bar shows WiFi and
BT dot indicators and a right-aligned estimated battery percentage (--% if
unavailable). A procedural Living Orb occupies the center. A reserved row shows
a round Leaf dot and the active Bluetooth host name only while HostService is
Ready and the Companion session is live. The bottom bar offers APPS and
SETTINGS; Left/Right selects one and Enter opens it. APPS is selected
whenever Home opens. Home has no clock or
connection-state text.
The default Enter action opens the AppRegistry-driven Launcher. The first registered
Mini App is SYSTEM, a read-only status list of battery, Bluetooth, host, Wi-Fi,
and firmware version. Escape returns from SYSTEM to Launcher and from Launcher
to Home. Plain Tab opens Settings; Bluetooth opens the existing BLE panel, Wi-Fi opens
manual network setup, and Sound volume adjusts persistent 0-100% key-click
volume in 10% steps. The remaining Settings rows control Screen timeout
(Normal: 15 seconds to dim, then 2 minutes to off; Long: 60 seconds, then
5 minutes; Never: always awake), Screen brightness (20%–100% in 10% steps),
and Unit Puzzle LED brightness (1%–10% in 1% steps). Left/Right changes each
value immediately; all three settings survive reboot.
Screen navigation uses short horizontal transitions with live input. The update loop polls semantic keyboard events, routes
local settings Actions, and advances HostService/BluetoothService plus
NetworkService/WiFiService independently of UI scheduling. Host selection,
BLE On/Off, pairing, renaming, per-host deletion, and persisted host
platform/capability/template-reference metadata are available through Services;
the current UI does not edit that metadata. Keyboard-to-HID mappings are not
yet implemented.
System Core also provides standalone navigation history, capability, and application-metadata registries plus record- and file-storage boundaries for later phases. The Cardputer microSD adapter compiles against the pinned board framework but is not constructed or mounted by the firmware runtime. These foundations include Mini App runtime/lifecycle, Launcher, and SYSTEM. They do not provide additional production Mini Apps, file-browser, backup, or configuration import/export behavior.
The Wi-Fi foundation provides a hardware-independent connection state machine and ESP32 station adapter. Normal firmware now composes both through NetworkService, which owns one bounded persisted station network, enabled intent, startup restoration, and credential-free domain status. Home and Wi-Fi Settings consume that Service snapshot. No credentials are compiled into firmware. Manual SSID/passphrase setup is available on the device; network scanning remains pending.
The Bluetooth lifecycle foundation similarly adds a hardware-independent
single-peer state machine and a compiled direct ESP-NimBLE peripheral
adapter. The same boundary now supports explicit authenticated pairing,
stable opaque bond references, a 16-bond registry, selected-bond reconnection,
explicit bond removal, and a shared hardware-neutral HID report contract. The
direct ESP-NimBLE adapter exposes a secured keyboard and consumer-control HID
service, accepts reports only for the selected authenticated and subscribed
peer, and releases active reports before controlled disconnects. HostService
composes this boundary with ConfigurationService: profiles, selection, and BLE
On/Off are stored in internal hub_config NVS. First use defaults to Off and
imports existing pairs without advertising. Switching closes the old connection
before allowing the selected host. Plain Tab opens Settings, whose Bluetooth entry exposes these
operations through ActionBus. A hardware-separated AudioService supplies
precomputed, asynchronous key clicks at a persistent default of 60%; see the
device guide.
The host-management suites cover persistence, selected-host switching and Off, error handling, pairing cancellation/completion, and local settings Actions through real Services with fake hardware. Physical two-host acceptance is tracked separately in plan 017.
Native tests run on the host and require no Cardputer hardware:
make testBehavior changes follow red-green-refactor TDD and should test observable behavior. The native suites cover stable firmware build metadata, log-level filtering, keyboard event translation and deduplication, opaque record-storage validation and forwarding, owned navigation history and Back traversal, dynamic capability registration and enumeration, owned application metadata validation and lookup, bounded logical file-storage operations, and System Core boot and update orchestration. They also cover version-6 configuration persistence and legacy migration, storage-before-radio failure atomicity, domain status, configuration validation, connection state, timeout and capped retry timing, connected-only and link-loss-safe RSSI access, disconnect-error propagation, and credential-free diagnostics. The Bluetooth suite covers side-effect-free construction, explicit lifecycle results, callback-event isolation, bonded single-peer policy, unbonded-peer rejection, pairing-window timing, all authenticated challenge modes, strict security completion, stable-reference finalization, capacity, target selection, bond deletion, reconnect timing, capped advertising backoff, fatal cleanup, stale event isolation, retained cleanup retries, and identity-free diagnostics. Peer-rejection coverage verifies that advertising cannot resume until all asynchronous disconnects for rejected peers have completed. The HID suites additionally cover neutral and six-key keyboard reports, consumer usages, invalid and duplicate usage rejection, selected-peer and dual subscription readiness, report ownership, retryable backpressure, neutral release, stale callbacks, controlled target changes, and clean re-enable.
Run formatting and static analysis separately with:
make format
make format-check
make lintBefore opening a pull request, run the complete CI-equivalent suite:
make checkmake check verifies the lock, formatting, Cppcheck analysis, native tests,
strict compiler warnings, and the Cardputer-Adv firmware build. make clean
removes ESP-IDF production build output. PlatformIO remains scoped to host-side
tests and analysis.
Connect the Cardputer-Adv with a USB-C cable that supports data. On a CRUB
multiboot device, write only the Hub application into CRUB's shared extra
slot:
make upload UPLOAD_PORT=<device>That command does not rewrite the bootloader, partition table, or otadata.
Do not run make upload-standalone or idf.py flash on a CRUB device: those
install Hub's standalone table and hide saved settings at 0x7a0000.
make upload refuses an image larger than the 6.5 MiB CRUB extra slot.
For a Hub-only device that has never used CRUB, the first installation or a
one-time upgrade from the earlier flash layout uses
make migrate-storage-layout UPLOAD_PORT=<device>. The explicit port ensures
the standalone upload and targeted erase reach the same Cardputer. It
provisions the standalone hub_config range; routine CRUB upgrades must
continue to use make upload so stored configuration is preserved. See the
installation guide for CRUB staging,
standalone migration, and release-asset flashing.
ESP-IDF's flash command normally resets the device automatically. If it cannot enter
download mode, hold the G0 button, press and release reset, release G0, and
retry the upload. You may need to grant access to the serial device on Linux.
make monitorWhen multiple serial devices are connected, list the ports with
python -m serial.tools.list_ports and select the Cardputer's fixed USB
Serial/JTAG port explicitly:
make monitor UPLOAD_PORT=<port>Replace <port> with the detected device path, such as /dev/ttyACM0 on Linux
or /dev/cu.usbmodem... on macOS.
The configured baud rate is 115200. Exit the monitor with Ctrl+].
Pull requests targeting any branch, pushes to main, and manual CI runs execute
the make host-check and make firmware-check portions of make check in
parallel on Ubuntu 24.04. This includes stacked pull requests whose base is
another feature branch. A final required status succeeds only when both paths
pass. CI prints the application and per-component size summaries, then uploads
those reports with the compiled application and partition-table images as an
artifact retained for seven days. Size is observable but does not yet have an
arbitrary pass/fail budget. All third-party Actions use full commit SHA pins,
and Dependabot proposes reviewed updates.
After CI validates a merged pull request on main, the protected
Release firmware workflow uses the source branch prefix to assign the next
semantic version: major/ or breaking/ bumps major, feat/ or minor/ bumps
minor, and fix/, perf/, or patch/ bumps patch. Other branch types do not
release firmware. The first eligible release is v0.1.0. The workflow repeats
full validation, embeds release metadata, creates the version tag, and
publishes the application image, its matching partition-table image, and
SHA256SUMS covering both files.
Only the two newest GitHub Release records and their assets are retained. All
official Git tags are preserved. To rebuild an older version, manually run
Rebuild tagged firmware with its existing vMAJOR.MINOR.PATCH tag. That
read-only workflow checks out the tag and provides the application image,
partition table, and checksums as a temporary seven-day artifact; it does not
recreate or delete Releases or tags.
The primary local commands are:
make setup resolve locked repository and ESP-IDF dependencies
make build compile Cardputer-Adv firmware
make firmware-size report application, partition, and component sizes for build/
make test run native tests
make format format owned C/C++ sources
make format-check verify formatting
make lint run static analysis
make host-check run lock, format, lint, and native test checks
make firmware-check
build firmware and verify its effective ESP-IDF configuration
make check run all required validation
make upload UPLOAD_PORT=<port>
compile and write Hub into the CRUB extra slot only
make upload-standalone
Hub-only factory flash; replaces the CRUB partition table
make migrate-storage-layout UPLOAD_PORT=<device>
one-time migration from the earlier standalone flash layout
make monitor open the 115200-baud serial monitor
make clean remove ESP-IDF production build output
See docs/ENGINEERING.md for the complete workflow.
Reviewed on 2026-09-15. The authoritative phase checklist records completed steps and remaining work; the plan index links implementation and validation history.
| Phase | Status |
|---|---|
| 1 — System Core | Complete |
| 2 — Connectivity | Software complete; physical acceptance partial |
| 3 — Core Services | HostService, v4 configuration with host/Wi-Fi data, NetworkService, battery and audio delivered; broader Services pending |
| 4 — Application Shell | Home, Settings, Launcher, live Wi-Fi status, key feedback, Tab navigation and page transitions delivered; power/remaining cues pending |
| 5 — Mini App Infrastructure | Partial; runtime, Launcher, and SYSTEM Mini App delivered; Service lifecycle composition pending |
| 6 — Device Manager | Built-in host list/pair/rename/delete/select delivered; full Mini App integration pending |
| 7–12 — Host Control, Companion, Weather, RGB, Remote, Extensions | Pending |
Normal firmware provides a BLE-only host connection, saved profiles and On/Off, Home telemetry, and local Settings. USB is for power, flashing and fixed serial diagnostics. USB HID and arbitration are removed; IHidTransport remains the future extension boundary. Services can persist bounded per-host platform/capability/template-reference metadata, but the UI does not edit it or execute mappings. On-device Wi-Fi status and manual setup are composed; clock synchronization, Wi-Fi scanning, Action-to-HID mappings, and a Mac companion are not implemented.
Physical checks confirmed fresh pairing, one local Off/On cycle, adding and
switching two computers, and reconnection to the last selected host after Reset
and power-on. The operator accepted the Home/navigation appearance.
Plan 017
keeps longer Off/reboot-Off checks, report/interruption reruns, long-duration
and equipment-limited cases, and USB serial hotplug open. Historical harness passes are not represented
as final-image acceptance. Latest local checks passed: 72 Python tests, 274
native tests, formatting, static analysis, and a clean ESP-IDF production
compilation. The final Plan-026 image is 1,146,080 bytes; detailed application
and component reports are available through make firmware-size.
Target device:
- M5Stack Cardputer-Adv
External hardware:
- M5Stack Unit Puzzle 8×8 WS2812E RGB LED matrix
- M5Stack Unit NFC (ST25R3916, SKU U216) for the NFC Mini App. It shares Grove port A with the Unit Puzzle, so use one at a time.
Additional sensors and modules may be added later.
License has not been selected yet.