Docs / Topics
Topics on the Bus
A reference for everything you can publish or subscribe to. If the event bus page explains how the bus works, this page explains what's on it — every hardware sensor, every output, every system event, with the payload format and who fires it.
Input — hardware → bus
btn/ — Buttons
Five GPIO buttons arranged as a joystick — up, down, left, right, and a centre press. The driver normalises active-low wiring so subscribers always see 1 = pressed.
- Published by
- Button driver
- Subscribed by
- Apps, launcher
- Payload
- (state)
Current pressed/released state of one button. Late subscribers receive the value immediately on subscribe, so an app can ask “is up held right now?” at any moment.
state is 0 (released) or 1 (pressed). Published on every transition plus once at boot.
- Published by
- Button driver
- Subscribed by
- Apps, launcher
- Payload
- (ts_ms)
Fires once when the button goes down. Use this for menu navigation, jump actions, anything that should happen on the press itself rather than the release.
ts_ms is the publish moment from the device millisecond clock.
- Published by
- Button driver
- Subscribed by
- Apps
- Payload
- (ts_ms, held_ms)
Fires once when the button comes back up. The driver always fires this regardless of how long the press was — handy when you want both a tap action and a release action.
- Published by
- Button driver
- Subscribed by
- Apps
- Payload
- (ts_ms)
A short press — pressed and released before the hold threshold. Mutually exclusive with hold: the driver decides at release time which of the two to fire.
Threshold defaults to 1500 ms.
- Published by
- Button driver
- Subscribed by
- Apps, launcher
- Payload
- (ts_ms, threshold_ms)
Fires once when a button has been held past the threshold. The launcher uses btn/center/hold as the “return to menu” gesture.
Defaults to 1500 ms. tap will not fire if hold did.
switch/ — Slide switches
Binary toggle switches. Different from buttons: there are no edges to listen for — only the current position.
- Published by
- Switch driver
- Subscribed by
- Apps
- Payload
- (position)
Current position of the switch. New subscribers receive it instantly; the bus republishes on every transition.
position is 0 or 1. No active-low normalisation — the raw pin value is reported.
analog/ — Analog sensors
Anything that produces a varying voltage — a dial, a light sensor, a thermistor. The driver reads the ADC, normalises the result to 0…1, and republishes only when the value crosses a deadband to avoid noise spam.
- Published by
- Analog driver
- Subscribed by
- Apps
- Payload
- (value)
Normalised reading from one analog channel. Inverted sensors are handled by swapping the min/max bounds at the driver layer, so apps always see 0 = low, 1 = high.
value is a float in [0.0, 1.0]. Default deadband 0.01.
imu/ — Motion (IMU)
A 6-axis accelerometer + gyro. The driver samples at 50 Hz and publishes both the raw values and a handful of derived gestures (shake, tilt, face-up orientation).
- Published by
- IMU driver
- Subscribed by
- Apps, host widgets
- Payload
- (ax, ay, az)
Acceleration in device coordinates. Includes gravity, so a stationary device reads ~1 g on the down axis.
Units: g. Cadence 50 Hz, deadband 0.02 g.
- Published by
- IMU driver
- Subscribed by
- Apps, host widgets
- Payload
- (gx, gy, gz)
Rotational rate in device coordinates. The driver auto-zeroes during the first ~500 ms of boot, so leave the device still while it powers up.
Units: degrees per second. Cadence 50 Hz, deadband 0.5 dps.
- Published by
- IMU driver
- Subscribed by
- Apps
- Payload
- (tilt_x, tilt_y)
Convenience output for apps that just want to tilt-to-move. Two clamped projections of the accel vector; no trigonometry required by the consumer.
Each axis is in [-1.0, 1.0].
- Published by
- IMU driver
- Subscribed by
- Apps
- Payload
- (intensity)
Fires once when the device is shaken hard enough. Apps use this as a refresh / scramble / reset gesture.
Triggers above 1.5 g sustained, with a 500 ms cooldown between fires.
- Published by
- IMU driver
- Subscribed by
- Apps
- Payload
- (face)
Fires when the device has been held in a new orientation for long enough to be intentional, not a wobble.
face is one of "top", "bottom", "left", "right", "front", "back". Requires a 300 ms hold.
mic/ — Microphone
An I²S MEMS microphone. The driver runs a bank of band-pass filters over the incoming audio and publishes the band magnitudes plus an overall level — enough for a sound-reactive visualiser without doing an FFT on the device.
- Published by
- Microphone driver
- Subscribed by
- Apps, host widgets
- Payload
- (b0, b1, … bN-1)
Magnitude of each log-spaced frequency band, low to high. The band count is configurable and defaults to 8 — which maps cleanly onto an 8-column display.
Each value is a float in [0.0, 1.0]. Published at about 30 Hz.
- Published by
- Microphone driver
- Subscribed by
- Apps
- Payload
- (rms)
Overall loudness — the RMS of the most recent audio window. A good single number for a VU meter or a level-triggered effect.
Float in [0.0, 1.0].
- Published by
- Microphone driver
- Subscribed by
- Apps
- Payload
- (peak)
Peak absolute sample over the most recent window — snappier than RMS, handy for transient or clap detection.
Float in [0.0, 1.0].
input/ — Text input (serial / host)
Characters and lines fed in from outside the device — a serial console, or a text-input widget in LumenLink. Useful for demos and debug input.
- Published by
- Serial driver
- Subscribed by
- Apps
- Payload
- (c)
One printable character at a time. Apps that want to react glyph-by-glyph subscribe to this.
Whitespace is stripped before publishing.
- Published by
- Serial driver
- Subscribed by
- Apps
- Payload
- (text)
A whole line at a time — published when the host sends a newline. Apps treating each line as a command subscribe here.
Trailing newline is stripped.
Output — bus → hardware
tone/ — Speaker
A single-voice piezo (or, on the simulator, the browser's audio output). Apps publish a tone or a sequence; the driver handles the timing. Boards without a speaker just drop the messages.
- Published by
- Apps
- Subscribed by
- Speaker driver
- Payload
- (hz, duration_ms)
Play one note. Replaces anything currently playing. A frequency of 0 means a rest — silence for the given duration.
- Published by
- Apps, LumenSynth
- Subscribed by
- Speaker driver
- Payload
- (dsl_str)
Play a short melody. The string is a space-separated list of note/duration pairs — e.g. "C5/100 E5/100 G5/100". Parse errors are logged and the sequence is dropped, not crashed.
Each token is note/ms. The note is a name — C4, F#5, Bb4 (sharp #, flat lowercase b) — or R for a rest. Durations are positive integers in ms. (Raw Hz is only accepted by tone/play, not here.)
- Published by
- Apps
- Subscribed by
- Speaker driver
- Payload
- ()
Cut playback immediately and clear the queue.
display/ — Display (LED matrix)
The pixel grid. Apps don't publish frames themselves — they draw into the framebuffer and let the display driver encode and ship the frame. The host only sees frames if it asks for them.
- Published by
- Display driver
- Subscribed by
- Host display widget
- Payload
- (width, height, gamma, wb_r, wb_g, wb_b)
Display metadata, published once on boot. The host display widget reads this to size its canvas and apply the same gamma / white-balance correction the device would.
Simulator uses an identity pipeline (gamma 1.0, white-balance 255,255,255).
- Published by
- Host (LumenDesigner, LumenLink)
- Subscribed by
- Display, launcher
- Payload
- (width, height, base64_rgb)
A host pushes a full frame to the device — this is how LumenDesigner previews a design on real hardware over Web Serial. While a host is driving this topic, the running app's own draws are held back so the injected frame shows through.
Pixels are row-major RGB triplets, base64-encoded, top-left first — the same encoding as display/out/frame, in the other direction.
- Published by
- Display driver
- Subscribed by
- Host display widget, LumenLink
- Payload
- (width, height, base64_rgb)
Keyframe — the full framebuffer base64-encoded. Republished every ~1 second by default, plus immediately when a new host subscribes. Late subscribers always have a baseline via the retained slot.
Pixels are row-major RGB triplets, base64-encoded. Top-left is the first pixel. Identity pipeline — no gamma or white-balance correction applied; the host renderer chooses what to do.
- Published by
- Display driver
- Subscribed by
- Host display widget, LumenLink
- Payload
- (base64_changes)
Delta frame — only the pixels that changed since the previous frame. Applied on top of the most recent keyframe by the consumer to reconstruct the current image. Lets the bus carry video-rate updates at low bandwidth, since most displays only change a handful of pixels per frame.
Changes are packed as 5 bytes per pixel: [idxHi, idxLo, R, G, B]. Index is row-major top-left, 0-based. Subscribers ignore deltas until they have a keyframe baseline.
System — lifecycle and diagnostics
sys/ — Boot, app lifecycle, driver gates
Topics that aren't tied to any particular sensor — they describe the state of the device itself, the launcher's current app, and switches that hosts use to gate drivers.
- Published by
- Serial driver
- Subscribed by
- LumenLink host
- Payload
- (protocol_version)
Published once at startup as a handshake. Hosts wait for this before sending anything else — it confirms the device is up and speaking the structured protocol.
- Published by
- Apps (via the screens helper)
- Subscribed by
- Launcher
- Payload
- ()
An app announces it's finished. The launcher catches this, tears down the app, and returns to the menu.
- Published by
- Menu, LumenLink
- Subscribed by
- Launcher
- Payload
- (target)
Ask the launcher to start an app. target is either an index into the app list or the app's exact name.
- Published by
- Menu, LumenLink
- Subscribed by
- Launcher
- Payload
- ()
Return to the menu from whatever app is running.
- Published by
- Launcher
- Subscribed by
- LumenLink, host UI
- Payload
- (app_name)
The name of the app that's currently running, or an empty string when sitting in the menu. Updated on every app switch.
- Published by
- Launcher
- Subscribed by
- LumenLink, host UI
- Payload
- (name_1, name_2, …)
All installed apps, in launch-index order. Hosts use this to build a “run app” UI without hard-coding names.
- Published by
- LumenLink, host UI
- Subscribed by
- IMU driver
- Payload
- (enabled)
A gate that lets a host take over a driver. Publishing 0 tells the IMU driver to stop sampling; the host can then inject fake accel readings (handy for testing or for a 3D cube widget).
- Published by
- LumenConfig, host UI
- Subscribed by
- Device
- Payload
- ()
Ask the device to restart. LumenConfig fires this after changing settings that only take effect on boot — new WiFi credentials, for instance.
- Published by
- Launcher
- Subscribed by
- Display + high-bandwidth sensor drivers
- Payload
- (mode)
How the display behaves. "normal" draws locally; "echo" and "echo-raw" also stream frames to a host over serial. Bandwidth-heavy drivers (IMU, analog) throttle themselves while an echo mode is active so the serial link doesn't saturate.
mode is one of "normal", "echo", "echo-raw".
- Published by
- Host (LumenLink)
- Subscribed by
- Display driver
- Payload
- (enabled)
A visual self-test toggle — when on, every non-black pixel is inverted, so you can confirm at a glance that a host is really driving the panel.
enabled is a boolean.
- Published by
- Host (LumenLink Display widget)
- Subscribed by
- Display driver
- Payload
- (enabled)
Asks the device to mirror every flush() as a display/out/frame keyframe. LumenLink's Display widget sets this on mount and clears it on unmount; off by default so an idle device doesn't burn USB-CDC bandwidth.
enabled is a boolean. The simulator ignores the gate and always publishes, so the widget works against either runtime.
log/ — Diagnostic logs
Three severity levels for diagnostic messages. The serial driver mirrors these to the host so logs from a running device show up in LumenLink's console without any special wiring.
- Published by
- Drivers, apps
- Subscribed by
- LumenLink console
- Payload
- (message)
Informational text — boot announcements, state changes worth noting.
- Published by
- Drivers, apps
- Subscribed by
- LumenLink console
- Payload
- (message)
Recoverable problems — a malformed tone sequence, an HTTP timeout. The app continues.
- Published by
- Drivers, apps
- Subscribed by
- LumenLink console
- Payload
- (message)
Something went wrong enough to matter — a crashed app, a fatal driver failure.
config/ — Runtime configuration
Live device settings, mirrored onto the bus. On boot the device publishes every value from its config.json; LumenConfig writes new values back when you save — so brightness, your name, timezone, and WiFi credentials are all just retained topics.
- Published by
- config_runtime, LumenConfig
- Subscribed by
- Drivers, apps
- Payload
- (value)
One configuration value. A late subscriber gets the current setting immediately; saving in LumenConfig republishes it so drivers and apps pick up the change live — e.g. user_brightness dims the panel without a reboot.
Keys include user_brightness, user_name, wifi_ssid, wifi_password, plus anything else in config.json. The value type depends on the key.
Network — WiFi and HTTP
wifi/ — WiFi state (ESP32 only)
Connection state, IP, signal strength, and error reporting for boards with WiFi (ESP32-class). Pico-class boards don't publish on these topics at all.
- Published by
- WiFi driver
- Subscribed by
- Apps, host UI
- Payload
- (state_str)
Current connection phase, updated on every transition.
One of "disconnected", "connecting", "connected", "failed".
- Published by
- WiFi driver
- Subscribed by
- Apps, host UI
- Payload
- (ip_address)
Dotted-quad IP once connected; empty string otherwise.
- Published by
- WiFi driver
- Subscribed by
- Apps, host UI
- Payload
- (rssi_dbm)
Signal strength in dBm — more negative is weaker. Polled at about 1 Hz and only republished when it moves by more than 3 dB.
- Published by
- WiFi driver
- Subscribed by
- Apps, host UI
- Payload
- (reason)
Diagnostic message on connection failure — timeouts, bad credentials, exceptions.
http/ — HTTP bridge
A small driver that turns topic publishes into HTTP requests, and HTTP responses back into topic publishes. Used for fetching weather, time, and random images, and for submitting scores.
- Published by
- HTTP bridge
- Subscribed by
- Host UI
- Payload
- (count)
Whether the bridge is mid-request. The host UI can use this to show a “fetching…” indicator.
1 while a request is open, 0 when idle.
weather/ — Weather endpoint
Outside conditions for a configured latitude/longitude. The bridge polls Open-Meteo every ten minutes or so and republishes a tidy summary on the bus.
- Published by
- HTTP bridge
- Subscribed by
- Apps
- Payload
- (label, temp_c)
Latest weather summary for the configured location.
label is one of: clear, cloudy, fog, drizzle, rain, snow, showers, thunder, unknown. Requires WEATHER_LATITUDE and WEATHER_LONGITUDE in config.py.
- Published by
- HTTP bridge
- Subscribed by
- Apps
- Payload
- (error_msg)
Reported when a fetch or transform fails. Apps can fall back to a placeholder.
- Published by
- HTTP bridge
- Subscribed by
- Debug
- Payload
- (json_string)
Untransformed JSON response, only published if the endpoint config opts in with keep_raw: True. Useful when debugging the transform.
time/ — Time endpoint
Wall-clock time for a configured timezone. The Pico has no RTC, so apps that want the time read this once and interpolate seconds locally with the millisecond clock.
- Published by
- HTTP bridge
- Subscribed by
- Apps (e.g. watch)
- Payload
- (hour, minute, second, day_of_week)
Current local time. Polled every ten minutes; apps that want sub-minute resolution interpolate.
hour 0–23, minute 0–59, second 0–59, day_of_week 0=Monday … 6=Sunday. Requires WORLD_TIME_TIMEZONE in config.py.
image/ — Random image endpoint
A pixel-art image, sized to the device display, fetched on demand from the LumenLab website. Used by the random-image app.
- Published by
- HTTP bridge
- Subscribed by
- Apps
- Payload
- (image_dict)
Latest fetched image. The dict contains the pixel map and metadata; the schema is documented at docs/api/random-image.md.
- Published by
- Apps
- Subscribed by
- HTTP bridge
- Payload
- ()
Ask for a fresh image. The bridge subscribes to this and fetches when it fires — e.g. on a shake or a button press.
- Published by
- HTTP bridge
- Subscribed by
- Apps
- Payload
- (error_msg)
Reported when the fetch or parse fails.
score/ — Score submission
Game apps publish to this topic when the player finishes. The HTTP bridge listens and POSTs the score to the leaderboard.
- Published by
- Apps
- Subscribed by
- HTTP bridge
- Payload
- (app_name, score)
Fire-and-forget submission. The bridge substitutes the configured USER_NAME into the request body. No response topic — the leaderboard updates on the website.