Skip to content

Repository files navigation

Apollo 2

Apollo, laurel-crowned, sipping an espresso

Firmware project that turns a small ESP32 touchscreen into a local controller for a La Marzocco Micra espresso machine — over Bluetooth, with no cloud dependency for day‑to‑day use.

Set your brew temperature, flip the steam boiler, put the machine on standby, watch the boiler come up to temperature, and (with a supported Bluetooth scale) run a live shot timer and flow graph — all from a dedicated little screen next to the machine instead of a phone app.

Apollo 2 magnet-mounted on a red Micra, Ferrari theme, just after a shot

The focus is on local control via bluetooth. Currently internet is only used for optional NTP.

Contents: Features · Getting started · Screenshots · Supported hardware · 3D prints · Developer documentation · Credits · License


Features

  • Local Micra control over Bluetooth — brew and steam temperatures, steam boiler on/off, standby, and live machine status. No cloud, no phone app.
  • Bluetooth scales — Bookoo Themis, Acaia (Umbra, Lunar, Prochef, Pyxis) and Varia Aku: live weight, shot timer, flow graph, tare, and the scale's own settings from the screen.
  • Brew by weight — stop the shot at a target weight on boards wired into the paddle circuit, or detect shots from the weight stream alone on any board. Finished shots freeze into a review graph.
  • Group care — auto‑flush after the cup lifts, flush on demand, and a backflush cleaning cycle, on wired boards.
  • Warm‑up chime — boards with a speaker play a tune when the machine is ready, so you can start it and walk away.
  • Shot history — every shot is saved to a microSD card as CSV, browsable on the device's Stats tab or from a phone via the device's own web page.
  • Settings backup — preferences copy to the same card and restore onto replacement hardware.
  • Over‑the‑air updates — the device checks for new firmware and installs it on request, with an optional beta channel.
  • Setup from your phone, no app — pairing and Wi‑Fi credentials go in through a page the device serves itself; scan the QR code on its screen.
  • Automatic time — NTP over your Wi‑Fi with a timezone picker, saved to the on‑board RTC where present.
  • Made to live on the counter — themes, °C/°F, 12/24‑hour clock, brightness, idle‑screen artwork; layouts from 4.3" to 8" panels.

Everything keeps working if the machine, the scale, or Wi‑Fi is absent — the UI just shows that part as offline.


Getting started

Important

Before buying any hardware, confirm you can get your machine's Bluetooth token. Apollo authenticates to the Micra with a token issued by the La Marzocco cloud. Grab it first with the LM Token app — sign in with your La Marzocco account and copy the token. If it comes back blank, the same tool can provision one over Bluetooth. Checking now avoids a nasty surprise after a board is already on your bench.

1. Get the hardware

Pick a board from the Which board? table below — three of them come as finished boxes that need no assembly at all. A Bluetooth scale is optional but unlocks the shot timer, flow graph, and brew‑by‑weight. Wiring the machine's paddle (for Auto shot) is optional too, and can always be added later — see the wiring guide.

2. Flash the firmware

No-toolchain option: the web flasher flashes any supported board straight from Chrome, Edge, or Firefox over USB — pick your board, click Install. Upgrading this way keeps your paired machine, Wi‑Fi and settings (unless you choose "Erase device"). Prebuilt images also live on the Releases page — those are full images, so flashing one with esptool does clear saved settings.

After the first flash Apollo updates itself over WiFi: it checks for new firmware, shows what changed, and installs on a tap. Tick Include pre-releases on the flasher (or turn on Settings → Apollo → WiFi → Beta updates on the device) to run beta builds instead — testing welcome, and a build that won't boot rolls back on its own.

Building from source requires PlatformIO (pio) and a USB cable.

make flash            # print selection of flash options
make flash-p4-5       # or target a board: p4-5 | p4-4-3 | s3-4-3c | p4-x-8
make monitor          # open the serial console (115200 baud)

3. Pair the machine

Settings → Micra → Bluetooth → Scan, then pick your machine. The device saves it, then asks for the machine's Bluetooth token (step 4) — the token is issued by the La Marzocco cloud and can't be read off the machine, so there's a short one‑time step to fetch it.

4. Enter your token

Tap Enter token on the prompt (or Settings → Micra → Set up) to start the device's own Wi‑Fi access point, Micra-Setup. Scan the QR code on the device's screen with your phone's camera — it joins the access point and the setup page pops up on its own. (Or join Micra-Setup manually and open http://192.168.4.1.) Paste your token and Save — the device connects and the access point closes on its own.

Where to get the token:

  • Download the LM Token app for your OS from the Releases page, unzip, and double-click it. Sign in with your La Marzocco account, pick your machine, and hit Copy token. This is the only step that uses the internet, and it runs on your computer. (Prefer a terminal? The lmtoken CLI is on the same page.)

Prefer to build LM Token / lmtoken from source (Go), or script it? See tools/lmtoken/README.md.

5. (Optional) Wi‑Fi + automatic time

On the same setup page you can enter your home Wi‑Fi name and password. The device then joins your network, gets an IP, and syncs the clock over NTP. Pick your city under Settings → Apollo → WiFi → Timezone. Auto‑sync can be turned off there too (Auto time (NTP)).

Because the setup page is always reachable from Set up WiFi, you can never be locked out if your network changes.

6. Find your way around

  • Home shows the machine (and scale, if paired). The large action button is Standby / Turn On when connected, and becomes a Connect button when the machine is disconnected. With a scale, the pill under the shot timer cycles the shot mode — Auto shot (wired paddle boards) / Shot detect / Manual — and becomes Reset while a finished shot is up for review.
  • Settings groups everything under Micra, Scale, and Device (brightness, clock, units, theme, Wi‑Fi).
  • Stats shows brew/boiler temperature history, the shot History log (SD‑card boards), and device info.

Every screen and setting is described in the user manual — it opens with a map of every screen and setting if you're looking for where something lives.


Screenshots

Home without a scale — brew/steam hero card Device display settings — brightness, theme, units

Home with a paired scale — weight, timer, flow graph Temperature history


Supported hardware

The firmware targets Waveshare ESP32 touch boards. One board is selected at build time.

Which board?

Four boards are supported, and each has its own firmware image. Every one of them delivers the full brew‑by‑weight experience with zero wiring (via Shot detect); the wiring column only matters if you also want Auto shot — the wired‑paddle mode where the machine's own paddle starts the shot and the firmware cuts it at target weight.

Pick Best for Screen Enclosure Auto‑shot wiring (optional) Performance
ESP32‑P4‑WIFI6‑Touch‑LCD‑5 (SKU 33762) Mounting on the Micra 5" 1280×720 3D‑printed shell DIY cable with an external opto module Best
ESP32‑P4‑WIFI6‑Touch‑LCD‑4.3 Mounting on the Micra, smaller screen 4.3" 800×480 3D‑printed shell DIY cable with an external opto module Best
ESP32‑S3‑Touch‑LCD‑4.3C BOX (SKU 33630) Mounting on the Micra with nothing to build 4.3" 800×480 Finished box Built‑in opto — three wires into screw terminals, nothing to build Good
ESP32‑P4‑WIFI6‑Touch‑LCD‑X 8" box Counter‑top companion 8" 1280×800 Finished box DIY cable with an external opto module Best

Start with the ESP32‑P4‑WIFI6‑Touch‑LCD‑5. It's the recommended mount‑on‑the‑Micra option: the P4 is much faster than the S3 (32 MB flash + 32 MB PSRAM against 16/8), and the 5" panel is the right size on the machine. The cost is two bits of DIY — you print a shell, and if you want Auto shot you make up a cable with an external opto module.

The ESP32‑P4‑WIFI6‑Touch‑LCD‑4.3 is the same board with a smaller screen. Identical electronics — same processor, radio, audio, battery path, SD slot and paddle wiring; only the panel differs (4.3" 800×480 instead of 5" 1280×720). Pick between the two on screen size and a small price difference, nothing else. It takes its own printed shell and its own firmware image.

Choose the S3‑4.3C BOX if you'd rather not print a case or build a cable. It's the only small board that arrives as a finished box and has the opto‑isolators built in, so Auto shot is three wires into screw terminals with no cable to assemble — genuinely the least‑hacking route to a fully wired setup. The trade is speed: it's the slower part. (The wiring guide covers both styles.)

The X 8" box is the counter‑top companion — the same P4 electronics behind the biggest screen, in a finished enclosure, for sitting beside the machine rather than on it. Its panel and silicon are verified; the Apollo image itself hasn't been confirmed on one yet.

Power, battery and RTC notes live in docs/HARDWARE.md.

3D‑printed stand, shells and mounts

Ready‑to‑print 3MF files live in hardware/3d-prints/:

File What it is
apollo2-stand.3mf Counter‑top stand. Mounts the S3‑4.3C‑BOX directly (it has the matching holes), and every shell below mounts to it the same way.
apollo2-magnet-mount.3mf Optional magnet mount — attaches the device to the Micra's top corner instead of the counter.
apollo2-wiring-gasket.3mf Optional wiring gasket — spaces the Micra's cover so the wiring can run underneath it.
esp32-p4-5-shell.3mf Shell for the ESP32‑P4‑WIFI6‑Touch‑LCD‑5.
esp32-p4-5-shell-slim.3mf Slimmer shell for the ESP32‑P4‑WIFI6‑Touch‑LCD‑5 — no battery compartment (USB‑power only).
esp32-p4-4.3-shell.3mf Shell for the ESP32‑P4‑WIFI6‑Touch‑LCD‑4.3.
esp32-p4-x-8-backplate.3mf Backplate that adapts the ESP32‑P4‑WIFI6‑Touch‑LCD‑X 8" box to the counter‑top stand above.

This short video shows how the magnet mount and wiring gasket fit together on the machine.

Fasteners:

  • Shells: 8 × M2.5×0.45, 5 mm screws each — 4 fasten the board into the shell, 4 fasten the shell cover.
  • Stand mount: 2 × M4‑0.7×8 mm screws — same spec whether you're mounting the S3‑4.3C‑BOX or any of the printed shells.
  • Magnet mount: 8 × 10 mm × 3 mm neodymium disc magnets.

Developer documentation

Architecture

The code is layered so the same UI runs on a real board and on a laptop:

include/core/        Pure interfaces (ports) + domain types. No LVGL, Arduino,
                     BLE, or SDL — just C++ and structs. e.g. IMachine, IScale,
                     IClock, INetwork, IProvisioner, IBrewController, IShotStore
                     (SD shot history), and the BLE central port (ble::ICentral).

src/core/            Portable protocol logic: the La Marzocco Micra link and
                     the Bookoo scale driver, written only against ble::ICentral
                     — so a new platform (Linux/BlueZ, Pico/btstack) reuses the
                     Bluetooth protocol code unchanged and implements only the
                     transport. Also the device-independent decisions: which
                     event makes which sound (sound.cpp), when a warm-up counts
                     as finished (ready_chime.h).

src/ui/              The LVGL user interface. Depends ONLY on core/ interfaces,
                     never on a concrete platform. Portable.

src/platform_esp32/  Device implementations of the core ports: the NimBLE GATT
                     transport, NVS config, display/touch drivers, Wi-Fi
                     station + NTP, the setup-portal web server.

src/platform_host/   "Fake" implementations that feed canned data, so the UI can
                     be built and rendered on a host with no hardware.

src/device/main.cpp  Device entry: wires the real implementations to ui::App.
src/sim/main.cpp     Simulator entry: wires the fakes, renders frames to PNG.

The UI is written against the core:: ports and is injected with concrete implementations at startup (App::build(...)). Swapping the real BLE machine for a FakeMachine is all that separates a board build from a laptop render — the UI code is byte‑for‑byte identical.

The simulator

No hardware needed. Builds a native executable that renders each screen/layout to renders/*.png:

make sim              # build + run, writes renders/*.png

This is the fastest way to iterate on UI: change code, make sim, look at the PNGs. Every supported screen size and several states are rendered.

The renders/ folder is git‑ignored build output; the screenshots in docs/img/ are curated copies, and the annotated manual images in docs/img/manual/ are generated from renders by make docs-img (tools/annotate_docs.py + its manifest). When the UI changes, run make sim, refresh the affected docs/img/*.png, and re-run make docs-img as part of the same change so the README and manual stay accurate.

Prerequisites

PlatformIO (pip install platformio) builds everything, and node (any recent LTS — brew install node) builds the shot-history web page the device serves. That page is embedded in the firmware as a generated header, include/platform_esp32/webapp_dist.h, which is not committed: every make build/make flash target rebuilds it when anything under tools/webapp/ changes, and the release workflow does the same, so a board can never serve a page that has drifted from the source. make sim needs no node — the simulator has no web server.

Building directly

pio run does not generate the web-app header (that dependency lives in the Makefile), so run make webapp first — or just use the make targets, which handle it.

pio run -e esp32-p4-micra-5       # 5"   1280x720 (P4, MIPI-DSI, WiFi6/BLE via C6)
pio run -e esp32-p4-micra-43      # 4.3"  800x480 (P4, MIPI-DSI, same board as the 5)
pio run -e esp32-s3-micra-4-3c    # 4.3"  800x480 (S3, RGB panel)
pio run -e esp32-p4-micra-x-8     # X-series 8" box (P4, 1280x800)
pio run -e sim                    # native simulator

platformio.ini also carries a few envs for boards that aren't released — they build, and they're kept as the record of how those boards differ. make build-release compiles the three published images; make build-all compiles every env.

Build environments and per‑board flags live in platformio.ini; the pin/panel definitions for each board are in include/platform_esp32/board_config.h.

Adding a board

Add an #elif defined(BOARD_...) block in board_config.h with the same constant names the drivers read (pins, panel size, feature macros), then add a matching [env:...] in platformio.ini with the -DBOARD_... flag. Driver code never hardcodes a pin — it reads board:: constants — so a new board is mostly a config block.

That makes the board buildable, not supported. Releasing one is a separate, deliberate step: a row in the .github/workflows/firmware-release.yml matrix (whose board value must equal the block's kUpdateSlug, since the self‑updater fetches <site>/<tag>/firmware/app/<slug>.bin), a card in site/index.html, and a row in the tables above. Releases are tagged vX.Y.Z; a vX.Y.Z-beta.N tag publishes the same images as a pre-release, listed only in releases-beta.json (the stable releases.json every device reads stays releases-only) — promoting one to stable is a rebuild at the real tag, never a copy of the artifacts, because fw::kVersion is compiled into the image. Publishing is gated: the workflow builds unattended, but the jobs that create the Release and push gh-pages (the OTA origin) run in a protected environment that holds the only write credential and waits for the owner's approval, and repository rulesets let only an admin touch gh-pages or a v* tag. Several envs stay on the buildable side on purpose.

Repository layout

include/core/          Domain interfaces + types
src/core/              Portable protocol implementations (Micra BLE, scales,
                       audio cues)
include/platform_esp32/ Device driver headers + board_config.h
include/platform_host/  Host fakes
include/ui/             UI headers (widgets, screen profiles, timezones)
include/vendor/         Vendored third-party headers (stb_image_write)
src/                    Implementations (see Architecture above)
hardware/3d-prints/     Printable stand, board shells + mounts (3MF)
tools/                  sim.sh, flash.sh, lmtoken (Go), PlatformIO helper scripts
renders/                Simulator output (PNG)

Credits & third‑party

This project stands on the work of others. Grateful thanks to:

  • pylamarzocco by Josef Zweck (MIT) — the reference for La Marzocco's Bluetooth protocol (GATT characteristic UUIDs, the JSON command/state payloads, machine name prefix) and the cloud auth flow that tools/lmtoken re‑implements in Go.
  • goscale (Apache‑2.0) — the model for the scale interface and the Bookoo Themis notification decode.
  • apollo — brew‑by‑weight / paddle‑stop approach that inspires the (in‑progress) brew controller.
  • Waveshare — board bring‑up details and register maps for the CH422G IO expander, GT911 / CST816 touch controllers, and the RGB panel timings, from their published ESP32‑S3 demos.
  • stb_image_write by Sean Barrett (public domain / MIT) — vendored in include/vendor/ for PNG output in the simulator; its license is retained in the file.

Library dependencies (fetched by PlatformIO): LVGL (MIT), NimBLE‑Arduino (Apache‑2.0), Arduino‑ESP32 (LGPL‑2.1 / Apache‑2.0), GFX Library for Arduino (BSD‑style), and ArduinoJson (MIT). Each retains its own license.


License

MIT © 2026 Marcus.

Not affiliated with or endorsed by La Marzocco. "La Marzocco" and "Micra" are trademarks of their respective owner; used here only to describe compatibility.

About

Second generation Micra remote and brew by weight

Resources

Stars

10 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages