Skip to content

Repository files navigation

Flight Station

A handheld aviation companion for the M5 Cardputer, built entirely with Claude Code.

Three modes under one launcher on a 240×135 screen:

  • ATC Radio — browse live air-traffic-control feeds (LiveATC) by ICAO search, nearest airport, region, or recently played, from an offline catalog flashed onto the device. Audio playback is currently broken — see Status.
  • Flight Board — a list-first view of live ADS-B aircraft within 25 nm of a configured home location, with a per-aircraft detail view.
  • TV Off — a rolling TV-B-Gone: walks a database of TV power-off codes over the Cardputer's IR LED so any TV in range sees its own power command.

Plus Settings (WiFi, home location, factory reset) and a first-run setup wizard.

Origin

This was built at Anthropic's Code with Claude event in Tokyo, where Anthropic handed out M5 Cardputers and challenged attendees to build something with Claude Code. The brainstorm, spec, plans, and implementation were all done in Claude Code sessions — the design docs it worked from are still in docs/superpowers/ and .scratch/flight-station/.

Hardware

M5 Cardputer-Adv (M5 StampS3A = ESP32-S3FN8, dual LX7 @ 240 MHz, 8 MB flash, 512 KB internal SRAM, no PSRAM). Firmware is kept portable to the original Cardputer.

  • Display 1.14" 240×135 ST7789V2, SPI, landscape. No vsync — full-screen repaints in a loop visibly tear/strobe, so every screen repaints only what changed.
  • Input 56-key QWERTY matrix. ; = up, . = down, Enter, ` = Esc/back, p = jump to Now Playing whenever audio is live.
  • Audio ES8311 codec + NS4150B amp + 3.5 mm jack, via M5.Speaker / M5Unified.
  • IR LED on GPIO 44, active low (it latches on if not parked off).
  • Radio WiFi 2.4 GHz only. No GPS — hence a configured "home" airport.

The no-PSRAM constraint is load-bearing. Audio buffers and the catalog JSON have to fit in internal SRAM after WiFi. Never set -DBOARD_HAS_PSRAM.

USB-CDC serial is dead on this board, so all device feedback is on the LCD. Flash cycles are the scarce resource when debugging.

Build & flash

pio run -e cardputer -t upload --upload-port /dev/cu.usbmodem1301
pio run -e cardputer -t uploadfs      # ATC catalog -> LittleFS (/catalog.json)

Toolchain notes that matter:

  • Platform is pinned to the pioarduino fork (55.03.39 → arduino-esp32 3.3.9). Mainline espressif32 resolves to core 2.0.x, which is too old for the keyboard.
  • board = m5stack-stamps3, default_8MB.csv partitions, littlefs filesystem.
  • IRremote comes in transitively via M5Cardputer 1.1.x — there is no explicit lib_deps entry.
  • lib/ESP32-audioI2S/ is a vendored esphome 2.3.0 fork. Upstream declares PSRAM mandatory and hard-fails on this board; the fork degrades to an 8 KB internal-RAM buffer and carries a one-line local patch to compile against arduino-esp32 3.x.

Host tests (pure logic, no hardware) run in the native env:

pio test -e native

20 Unity suites, 75 test cases, all passing. They cover geo, units, ADS-B parse, aircraft classification, catalog parse, ATC search/nearest/regional/recent/feed-sort/edge-URL, the PCM ring's byte-loss invariant, the nav stack, app state, coords, ICAO search, the airport index, and the Flight Board controller/format/view.

Repo layout

src/
  main.cpp            shell loop: nav stack, redraw gate, live-screen dispatch
  shell/              launcher, settings, TV Off screen, keys, chrome, NVS, nav stack
  atc/                catalog store, browse screens, audio engine + sources, PCM ring
  flightboard/        controller, view logic, formatting, render
  services/           adsb.fi client + parser
  provisioning/       WiFi scan/connect, home entry (ICAO or lat/lon), first-run wizard
  ir/                 TV-B-Gone send logic + vendored code database
  model/, data/       geo, units, aircraft classification, home-airport index
tools/catalog_generator/   host-side Python: LiveATC feed index + OurAirports -> catalog.json
test/native/               Unity host tests
src_spike/, examples/      isolated hardware bring-up spikes
docs/superpowers/          spec + implementation plans

Pure logic is deliberately split from anything touching Arduino, so most of the interesting code is testable on the host. The native build filter in platformio.ini is the boundary.

ATC catalog

The device never scrapes LiveATC. A host-side generator builds data/catalog.json (airports

  • feed mounts, size-budgeted for SRAM) and a matching src/data/airports.cpp home index:
cd tools/catalog_generator
pip install -e '.[dev]'
catalog-gen --airports-csv path/to/airports.csv --continents OC,EU
pytest

The generator caches, rate-limits, and uses a browser-like UA. data/catalog.json is gitignored.

Status

ATC Radio audio is broken. Everything else works on device.

Mode State
Shell, launcher, provisioning wizard, Settings working
Flight Board (live ADS-B, list + detail) working
TV Off (IR blast, all four runs) working
ATC Radio — catalog, browse, feed list, now-playing UI, nav working
ATC Radio — audio playback broken

On the ATC audio path: the ESP8266Audio route was abandoned after four flash cycles with decode pinned at ~25% of real time (continuous but choppy and unreadable). libmad, the wire stream, and the socket were each exonerated — the failure is structural, in the flow-control seam between AudioGeneratorMP3's pull model and a non-blocking trickle source. The pivot to vendored ESP32-audioI2S is spiked (pio run -e spike_audioi2s) and builds, but has not been proven to drive the ES8311 through the I2S hand-off.

Read docs/superpowers/specs/atc-audio-debug-state.md before touching the audio path — it records what has been definitively ruled out, so those cycles aren't spent twice. Flash cycles are the budget; there is no serial console on this board.

Other known rough edges:

  • screens::flight_board in src/shell/screens.cpp is a leftover stub. The real Flight Board runs through main.cpp's live-screen path.
  • src/data/airports.cpp currently holds only the two Sydney airports it was generated with. Re-run the catalog generator for a wider home index.

Legal

Personal, non-distributed proof of concept.

  • LiveATC ToS forbids use of streams in third-party products. Accepted on a personal-use basis: do not publish or distribute firmware, images, or a catalog with LiveATC streams baked in; never re-host or proxy a stream; stream only while actively listening.
  • src/ir/world_ir_codes.h is vendored third-party data under Creative Commons 2.5 Attribution & Share Alike (not the AGPL covering Bruce overall). Keep the notice and the Altman/Fried/Shirriff/Grimpelhuber/Bruce attribution chain intact.
  • lib/ESP32-audioI2S/ carries its own upstream license.

About

Handheld aviation companion for the M5 Cardputer — live ATC radio, nearby-aircraft flight board, and a rolling TV-B-Gone. Built with Claude Code at Code with Claude Tokyo.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages