Pure-Python driver for the Abstract Foundry LumiCube. Talks the reverse-engineered wire protocol directly over /dev/ttyAMA0 — no Java daemon required.
Project description
pylumicube
Pure-Python driver for the Abstract Foundry LumiCube.
Speaks the reverse-engineered wire protocol directly over /dev/ttyAMA0,
so you don't need the original Java foundry-daemon AppImage to drive
the panel. Protocol details are in PROTOCOL.md.
Status
First milestone reached: lighting up the LED matrix from Python works end-to-end on real hardware (verified 2026-05-10).
Done
- Link layer. Bidirectional PING/PONG handshake with the firmware's 256-PONG drain honoured, INITIALISE/INITIALISED, 16-slot sliding window with retransmits.
- Node discovery. Passive harvest from
NODE_STATUSbroadcasts; the 3-stage dynamic node-ID allocator is implemented and ready for cold-boot scenarios. - Module discovery.
GET_PREFERRED_NAMEto pick thecubebase board (which owns the LED matrix) over thebutton_and_light_sensorboard. - LED matrix.
SET_FIELDSwrites covering all 192 LEDs split across 3 frames. Exposed as thelumicube-ledsCLI and theLumiCube.displayAPI. - Schema discovery.
ENUMERATE_FIELDSwalker (scripts/snapshot_hardware.py) that decodes the cube's in-line sub-dicts and resolves block floors via probing + binary search. Wire semantics inPROTOCOL.md§4.5.1.
Todo (roughly ascending complexity)
- Run upstream Python scripts against pylumicube — e.g.
digital_clock_v1.py. Provide a compat shim mirroring the abstractfoundry client API so existing community scripts work unchanged. - Microphone input. Implement the
SUBSCRIBE_DEFAULT_FIELDS+PUBLISHED_FIELDStelemetry plumbing first, then expose themicrophone.datastream. - Light sensor. Colour, proximity, and gesture readings from the
button_and_light_sensorboard (telemetry-driven, builds on item 2). - Secondary LCD screen. Drive the
screenmodule on the cube node. Also forces the move from a hardcoded display schema to runtimeENUMERATE_FIELDS+ direct-probe discovery (seePROTOCOL.md§5.1). - FastAPI daemon. Replace the Java
foundry-daemonwith a Python REST API (Swagger-documented), shipped as a systemd unit. - Web frontend for the daemon.
Protocol-side open questions tracked in PROTOCOL.md §7.
Install
From PyPI (once released):
pip install pylumicube
From source — with uv:
git clone https://github.com/chrislibuilds/pylumicube.git
cd pylumicube
uv sync
Or with pip in any 3.11+ venv:
git clone https://github.com/chrislibuilds/pylumicube.git
cd pylumicube
pip install -e .
The only runtime dependency is pyserial.
CLI
The entry point is lumicube-leds (equivalent to python -m pylumicube.cli).
The Java foundry-daemon must not be running — it holds /dev/ttyAMA0
exclusively.
# Set every LED to red
lumicube-leds all FF0000
# Set LED 42 to green
lumicube-leds single 42 00FF00
# Off
lumicube-leds off
# Custom port + verbose logging
lumicube-leds --port /dev/ttyAMA0 --debug all 0000FF
Library
from pylumicube import LumiCube
with LumiCube('/dev/ttyAMA0') as cube:
cube.display.fill(0x00FF00)
cube.display.set_leds({0: 0xFF0000, 1: 0xFFFFFF, 2: 0x000000})
Testing
pytest tests/
Tests cover COBS, CRC, framing, FlatDictionary, UAVCAN messageId encoding, and an end-to-end handshake against a fake serial emulator — no hardware required.
Project layout
src/pylumicube/
constants.py # protocol constants
cobs.py # in-place COBS (matches Java)
crc.py # CRC-16/CCITT-FALSE
framing.py # delimited frame builder + parser
link.py # SerialLink: handshake + sliding window
uavcan.py # messageId encoding/decoding
transport.py # Transport: transferIds, request/response
flat_dictionary.py # FlatDictionary TLV encoder/decoder
metadata.py # FieldSpec + hardcoded display schema
allocator.py # 3-stage dynamic node-ID allocator
node.py # LumiCube top-level API
display.py # Display module helpers
cli.py # CLI tool
tests/ # offline pytest suite
scripts/ # on-device debug + bring-up helpers
PROTOCOL.md # canonical protocol spec
CHANGELOG.md # versioned change history
LICENSE # GPL-3.0
Scripts
Helper utilities under scripts/ (require a connected cube):
snapshot_hardware.py— walk every node'sENUMERATE_FIELDSschema and print one row per field. Useful as a reference dump.query_key.py <key> [...]— query a single field's metadata by absolute wire key.dump_metadata.py,find_leds.py,probe_set_fields.py— earlier debug helpers from the reverse-engineering work; kept for posterity.check_pi_uart.sh— verify a fresh Raspberry Pi OS image is ready to talk to the LumiCube via/dev/ttyAMA0(correct boot config, no serial console, no daemon installed, etc.).
Compatibility
- Python 3.11 or newer.
- Linux (tested on Raspberry Pi OS Bookworm). Other POSIX platforms
should work if you can open
/dev/ttyAMA0at 3 Mbaud. - LumiCube firmware as shipped with the AppImage 2.0.1 image. Older firmware revisions may use different field-key layouts.
Contributing
Pull requests welcome. Please:
- Keep changes covered by
pytest tests/. - Update
PROTOCOL.mdwhen you discover something new about the wire format. - Note user-visible changes in
CHANGELOG.md.
License
GPL-3.0-or-later. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pylumicube-0.1.2.tar.gz.
File metadata
- Download URL: pylumicube-0.1.2.tar.gz
- Upload date:
- Size: 42.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b4cc5e80a643ac3dba389e123c22a34ce7ec4a88dffc0196a2c32c31751b1f0
|
|
| MD5 |
6ff971c3b5eb3dc3975d20fc8b9c6d38
|
|
| BLAKE2b-256 |
89d53c14edeebf68e54533a33db09fa3fec4cd50b490502c9bfc187cbd47b21e
|
Provenance
The following attestation bundles were made for pylumicube-0.1.2.tar.gz:
Publisher:
release.yml on chrislibuilds/pylumicube
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pylumicube-0.1.2.tar.gz -
Subject digest:
3b4cc5e80a643ac3dba389e123c22a34ce7ec4a88dffc0196a2c32c31751b1f0 - Sigstore transparency entry: 1635251603
- Sigstore integration time:
-
Permalink:
chrislibuilds/pylumicube@c3cb029f062cd91ae6d05bdc5068f505268e1aa6 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/chrislibuilds
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c3cb029f062cd91ae6d05bdc5068f505268e1aa6 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pylumicube-0.1.2-py3-none-any.whl.
File metadata
- Download URL: pylumicube-0.1.2-py3-none-any.whl
- Upload date:
- Size: 39.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b0d4d4d65bf48d7318691777fb1798919e10aa7a39aa83b204034412a50faaf
|
|
| MD5 |
9120df561565f17f93ef06855dfc8cb8
|
|
| BLAKE2b-256 |
2b55ca5a1d57784310666cd28a952f9c4cfac2c55e7982121bb38899c1678784
|
Provenance
The following attestation bundles were made for pylumicube-0.1.2-py3-none-any.whl:
Publisher:
release.yml on chrislibuilds/pylumicube
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pylumicube-0.1.2-py3-none-any.whl -
Subject digest:
3b0d4d4d65bf48d7318691777fb1798919e10aa7a39aa83b204034412a50faaf - Sigstore transparency entry: 1635251627
- Sigstore integration time:
-
Permalink:
chrislibuilds/pylumicube@c3cb029f062cd91ae6d05bdc5068f505268e1aa6 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/chrislibuilds
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c3cb029f062cd91ae6d05bdc5068f505268e1aa6 -
Trigger Event:
push
-
Statement type: