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.

Legend:retainedcarries state — late subscribers get the last value.·transientfires once.

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.

btn/name/stateretained
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.

btn/name/presstransient
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.

btn/name/releasetransient
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.

btn/name/taptransient
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.

btn/name/holdtransient
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.

switch/name/stateretained
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.

analog/name/valueretained
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).

imu/accelretained
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.

imu/gyroretained
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.

imu/tiltretained
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].

imu/shaketransient
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.

imu/orienttransient
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.

mic/bandsretained
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.

mic/level/rmsretained
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].

mic/level/peakretained
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.

input/chartransient
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.

input/linetransient
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.

tone/playtransient
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.

tone/sequencetransient
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.)

tone/stoptransient
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.

display/metaretained
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).

display/in/frameretained
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.

display/out/frameretained
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.

display/out/frame/deltatransient
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.

sys/bootretained
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.

sys/exittransient
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.

sys/app/launchtransient
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.

sys/app/exittransient
Published by
Menu, LumenLink
Subscribed by
Launcher
Payload
()

Return to the menu from whatever app is running.

sys/app/currentretained
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.

sys/apps/listretained
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.

sys/driver/imu/enabledretained
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).

sys/reboottransient
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.

sys/display_moderetained
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".

sys/display/invert_testretained
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.

sys/display/mirror_outretained
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.

log/infotransient
Published by
Drivers, apps
Subscribed by
LumenLink console
Payload
(message)

Informational text — boot announcements, state changes worth noting.

log/warntransient
Published by
Drivers, apps
Subscribed by
LumenLink console
Payload
(message)

Recoverable problems — a malformed tone sequence, an HTTP timeout. The app continues.

log/errortransient
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.

config/value/keyretained
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.

wifi/stateretained
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".

wifi/ipretained
Published by
WiFi driver
Subscribed by
Apps, host UI
Payload
(ip_address)

Dotted-quad IP once connected; empty string otherwise.

wifi/rssiretained
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.

wifi/errortransient
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.

http/in_flightretained
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.

weather/currentretained
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.

weather/current/errortransient
Published by
HTTP bridge
Subscribed by
Apps
Payload
(error_msg)

Reported when a fetch or transform fails. Apps can fall back to a placeholder.

weather/current/rawretained
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.

time/localretained
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.

image/randomretained
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.

image/random/refreshtransient
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.

image/random/errortransient
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.

score/submittransient
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.

Adding a new topic? Keep the prefix-then-purpose convention (sensor first, action last), pick retained or transient consciously, and document it here.