Temporal Badge MicroPython API Reference
All functions and constants from the badge module are automatically imported
into the global namespace. You can call them directly (e.g. button(BTN_CONFIRM))
without needing the badge. prefix.
Init:
init()— Initialize the badge hardware bridge (called automatically)
oled_print(text)— Print text at cursoroled_println(text)— Print text + newline + showoled_clear([show])— Clear displayoled_show()— Refresh displayoled_set_cursor(x, y)— Set text cursoroled_set_text_size(size)— Set text size (1–4)oled_get_text_size()— Get text sizeoled_invert(enable)— Invert display colorsoled_text_width(text)— Get pixel width of a stringoled_text_height()— Get pixel height of current fontoled_set_font(name)— Set font by nameoled_get_fonts()— Get available font namesoled_get_current_font()— Get current font nameoled_set_pixel(x, y, color)— Set single pixeloled_get_pixel(x, y)— Read single pixeloled_draw_box(x, y, w, h)— Draw filled rectangleoled_set_draw_color(color)— Set draw color (0=black, 1=white, 2=XOR)oled_get_framebuffer()— Get framebuffer as bytesoled_set_framebuffer(data)— Set framebuffer from bytesoled_get_framebuffer_size()— Get (width, height, bytes)
ui_header(title, [right])— Draw the standard header and ruleui_action_bar([left_button], [left_label], [right_button], [right_label])— Draw footer actions with native button glyphsui_chrome(title, [right], [left_button], [left_label], [right_button], [right_label])— Clear and draw standard header/footer chromeui_inline_hint(x, y, hint)— Draw an inline hint with native button glyphsui_inline_hint_right(right_x, y, hint)— Right-align an inline hintui_measure_hint(hint)— Return an inline hint's pixel width
mouse_overlay(enable)— Enable/disable cursor overlaymouse_set_bitmap(data, w, h)— Set cursor sprite (row-major bitmap, max 32×32)mouse_x()— Current cursor X positionmouse_y()— Current cursor Y positionmouse_set_pos(x, y)— Warp cursor to positionmouse_clicked()— Read last click button ID (-1 if none)mouse_set_speed(speed)— Set cursor speed (1–20, default 3)mouse_set_mode(mode)— Set positioning mode (MOUSE_ABSOLUTEorMOUSE_RELATIVE, defaultMOUSE_RELATIVE)
button(id)— Read button state (True if held)button_pressed(id)— Edge-triggered press (True once per press)button_held_ms(id)— Milliseconds button has been heldjoy_x()— Joystick X axis (0–4095)joy_y()— Joystick Y axis (0–4095)
LED Matrix (8×8):
led_brightness(value)— Set global brightness (0–255)led_clear()— Turn off all LEDsled_fill([brightness])— Turn on all LEDsled_set_pixel(x, y, brightness)— Set single LEDled_get_pixel(x, y)— Read single LED brightnessled_show_image(name)— Show builtin image by nameled_set_frame(rows, [brightness])— Draw 8×8 bitmask patternled_start_animation(name, [interval_ms])— Start builtin animationled_stop_animation()— Stop current animationled_override_begin()— Pause ambient LED mode for foreground drawingled_override_end()— Release foreground drawing and restore ambient LEDs
Matrix App Host (Background LED Callbacks):
matrix_app_start(callback, [interval_ms], [brightness])— Register a Python callback for background LED matrix renderingmatrix_app_set_speed(interval_ms)— Change the tick intervalmatrix_app_set_brightness(brightness)— Change the LED brightnessmatrix_app_stop()— Unregister the callbackmatrix_app_active()— Check if a callback is registeredmatrix_app_info()— Return diagnostic tuple
App Manifest (Folder-app metadata):
__title__,__description__,__icon__,__matrix_title__,__order__— Top-level dunders read from/apps/<slug>/main.pyto decorate the main menu tileicon.py— 12×12 packed-XBMDATAtuple for the home-screen iconmatrix.py— Persistent LED-matrix script registered viamatrix_app_startrescan_apps()— Hot-refresh the registry after editing manifests
IMU:
imu_ready()— Check if IMU is initializedimu_tilt_x()— X-axis tilt in milli-gimu_tilt_y()— Y-axis tilt in milli-gimu_accel_z()— Z-axis acceleration in milli-gimu_face_down()— True if badge is face-downimu_motion()— Consume motion event (True if motion detected since last call)
Haptics (Vibration Motor + Coil Tone):
haptic_pulse([strength], [duration_ms], [freq_hz])— Fire vibration pulsehaptic_strength([value])— Get or set motor strength (0–255)haptic_off()— Stop motortone(freq_hz, [duration_ms], [duty])— Play audible tone from motor coilno_tone()— Stop tonetone_playing()— Check if tone is active
IR Send/Receive (NEC Protocol):
ir_send(addr, cmd)— Transmit one 1-byte-addr / 1-byte-cmd NEC frameir_start()— Start IR receive modeir_stop()— Stop IR receive, flush queueir_available()— Check if a received frame is waitingir_read()— Read received (addr, cmd) as a tupleir_send_words(words)— Transmit a multi-word NEC frame (1–64 × 32-bit)ir_read_words()— Read a received multi-word NEC frame as a tupleir_flush()— Drop every pending RX frameir_tx_power([percent])— Get/set IR carrier duty (1–50%)
my_uuid()— Return this badge's 12-char hex UIDboops()— Return/boops.jsoncontents as a string
exit()— RaiseSystemExitto cleanly stop the running appdev(*args)— Test harness dispatcher (dev builds only)
- Standard
osmodule —listdir,mkdir,remove,rename,stat, etc. - Standard
open()/read()/write()/close()
Init
init()
Initialize the badge hardware bridge. This is called automatically when the
badge module loads — you should not normally need to call it. Returns 0 on
success; raises OSError on failure.
OLED Display
128×64 monochrome SSD1306 OLED display controlled via U8G2.
oled_print(text)
Print text at the current cursor position. Does not refresh the display
automatically — call oled_show() to make it visible.
text: String to display.
oled_println(text)
Print text followed by a newline, then automatically refresh the display.
text: String to display.
Example:
oled_clear()
oled_println("Hello, badge!")
oled_println("Line 2")
oled_clear([show])
Clear the display and reset cursor to (0, 0).
show(optional): IfTrue, refresh display immediately. DefaultFalse.
Example:
oled_clear() # Clear buffer only
oled_clear(True) # Clear and refresh immediately
oled_show()
Refresh the display to show buffered changes. Required after oled_print(),
oled_set_pixel(), or oled_set_framebuffer() to make changes visible.
oled_set_cursor(x, y)
Move the text cursor to pixel coordinates.
x: X position (0–127).y: Y position (0–63).
oled_set_text_size(size)
Set the text rendering size.
size: Text size multiplier (1–4). ReturnsTrueon success.
oled_get_text_size()
Returns the current text size (1–4).
oled_invert(enable)
Invert the display colors.
enable:Trueto invert,Falsefor normal.
oled_text_width(text)
Get the pixel width of a string in the current font.
text: The string to measure.- Returns the width in pixels.
Example:
w = oled_text_width("Hello")
x = (128 - w) // 2
oled_set_cursor(x, 30)
oled_print("Hello")
oled_show()
oled_text_height()
Get the maximum character height of the current font.
- Returns the height in pixels.
oled_set_font(name)
Set the font by name.
name: Font family name (case-sensitive).- Returns
Trueif font was found,Falseotherwise.
oled_get_fonts()
Returns a comma-separated string of available font names.
Example:
fonts = oled_get_fonts().split(",")
for f in fonts:
oled_set_font(f)
oled_clear()
oled_println(f)
import time; time.sleep(1)
oled_get_current_font()
Returns the name of the currently active font.
oled_set_pixel(x, y, color)
Set a single pixel in the framebuffer.
x: X coordinate (0–127).y: Y coordinate (0–63).color:1for white/on,0for black/off.
Call oled_show() after setting pixels to make changes visible.
oled_get_pixel(x, y)
Read the color of a single pixel.
- Returns
1(white/on) or0(black/off).
oled_draw_box(x, y, w, h)
Draw a filled rectangle in the framebuffer using the current draw color.
x: Left edge (0–127).y: Top edge (0–63).w: Width in pixels.h: Height in pixels.
Call oled_show() after drawing to make changes visible.
Example:
oled_clear()
oled_draw_box(10, 10, 50, 20)
oled_set_draw_color(0)
oled_set_cursor(12, 12)
oled_print("Hello")
oled_set_draw_color(1)
oled_show()
oled_set_draw_color(color)
Set the drawing color for subsequent draw operations.
color:0= black/off,1= white/on (default),2= XOR (inverts existing pixels).
oled_get_framebuffer()
Returns the entire display framebuffer as a bytes object. Format: 1 bit per
pixel, organized in vertical bytes (Adafruit SSD1306 format).
oled_set_framebuffer(data)
Replace the entire display framebuffer and refresh.
data:bytesorbytearrayof the correct size (typically 1024 bytes for 128×64).- Returns
Trueon success,Falseon size mismatch.
oled_get_framebuffer_size()
Returns a tuple (width, height, buffer_size_bytes).
Example:
w, h, size = oled_get_framebuffer_size()
print(str(w) + "x" + str(h) + ", " + str(size) + " bytes")
Native UI Chrome
These helpers draw the same header, footer, and button glyph style used by
the firmware screens. App code should usually import badge_ui, which wraps
these native functions with Python conveniences like ui.chrome(...).
badge_ui also includes ui.hint(...), ui.hint_text(...), and
ui.hint_row(...) helpers for composing multiple glyph-backed action hints
without hand-building strings throughout an app.
ui_header(title, [right])
Draw the standard small header and divider. right is optional top-right text.
ui_action_bar([left_button], [left_label], [right_button], [right_label])
Draw footer actions with native button glyphs. Button names include OK,
BACK, X, Y, A, and B; semantic names respect the badge's confirm/back
swap setting.
ui_chrome(title, [right], [left_button], [left_label], [right_button], [right_label])
Clear the OLED buffer, draw the standard header, then draw the footer action
bar. Call oled_show() after drawing your screen content.
ui_inline_hint(x, y, hint)
Draw inline text with native button glyph replacement, such as "OK:start" or
"BACK quit". Returns the drawn width in pixels.
ui_inline_hint_right(right_x, y, hint)
Right-align an inline hint to right_x. Returns the drawn width in pixels.
ui_measure_hint(hint)
Return the pixel width an inline hint will use.
Buttons & Joystick
Four face buttons and a 2-axis analog joystick. The directional constants are kept for compatibility; new apps should prefer the semantic aliases where they fit the interaction.
Button Constants
BTN_RIGHT = 0
BTN_DOWN = 1
BTN_LEFT = 2
BTN_UP = 3
BTN_CIRCLE = BTN_RIGHT
BTN_CROSS = BTN_DOWN
BTN_SQUARE = BTN_LEFT
BTN_TRIANGLE = BTN_UP
BTN_CONFIRM = 4
BTN_SAVE = BTN_CONFIRM
BTN_BACK = 5
BTN_PRESETS = BTN_TRIANGLE
BTN_CONFIRM/BTN_SAVE and BTN_BACK follow the firmware
swap_ok setting: the default is B/Circle confirm and A/Cross back; setting
swap_ok = 0 uses A/Cross confirm and B/Circle back. The physical constants and
PlayStation-style shape constants always refer to the actual hardware button.
button(id)
Read the current state of a button.
id: Button constant (BTN_CONFIRM,BTN_BACK,BTN_PRESETS, etc.).- Returns
Trueif the button is currently held down.
Example:
if button(BTN_CONFIRM):
oled_println("confirm held!")
button_pressed(id)
Edge-triggered button press detection. Returns True once per physical
press, then False until the button is released and pressed again. Consumes
the press event on read.
id: Button constant.- Returns
Trueif a new press was detected since the last call.
Example:
import time
count = 0
while True:
if button_pressed(BTN_CONFIRM):
count += 1
oled_clear()
oled_println("Presses: " + str(count))
time.sleep(0.02)
button_held_ms(id)
Get how long a button has been continuously held.
id: Button constant.- Returns milliseconds the button has been held, or
0if not pressed.
Example:
ms = button_held_ms(BTN_DOWN)
if ms > 1000:
oled_println("Long press!")
joy_x()
Read the joystick X axis.
- Returns an integer 0–4095. Center is approximately 2047.
joy_y()
Read the joystick Y axis.
- Returns an integer 0–4095. Center is approximately 2047.
Example:
x = joy_x()
y = joy_y()
oled_clear()
oled_println("X:" + str(x) + " Y:" + str(y))
LED Matrix (8×8)
8×8 LED matrix driven by the IS31FL3731 with per-pixel PWM brightness control.
Image Constants
Builtin image names for use with led_show_image():
IMG_SMILEY = "smiley"
IMG_HEART = "heart"
IMG_ARROW_UP = "arrow_up"
IMG_ARROW_DOWN = "arrow_down"
IMG_X_MARK = "x_mark"
IMG_DOT = "dot"
Animation Constants
Builtin animation names for use with led_start_animation():
ANIM_SPINNER = "spinner"
ANIM_BLINK_SMILEY = "blink_smiley"
ANIM_PULSE_HEART = "pulse_heart"
led_brightness(value)
Set the global LED brightness.
value: Brightness level (0–255).0is off,255is maximum.
led_clear()
Turn off all LEDs on the matrix.
led_fill([brightness])
Turn on all LEDs. If brightness is omitted, uses the current global brightness.
brightness(optional): Per-pixel brightness (0–255).
led_set_pixel(x, y, brightness)
Set a single LED brightness.
x: Column (0–7).y: Row (0–7).brightness: LED brightness (0–255).
Example:
led_clear()
led_set_pixel(3, 3, 100)
led_set_pixel(4, 4, 100)
led_get_pixel(x, y)
Read the brightness of a single LED.
- Returns brightness value (0–255).
led_show_image(name)
Display a builtin image on the matrix.
name: Image name string (see Image Constants above).- Returns
Trueif the image was found.
Example:
led_show_image(IMG_HEART)
import time
time.sleep(2)
led_show_image("smiley") # string name works too
led_set_frame(rows, [brightness])
Draw an arbitrary 8×8 pattern from a list of row bitmasks. Each row is a uint8 where the MSB is the leftmost pixel.
rows: List or tuple of exactly 8 integers (0–255), one per row.brightness(optional): On-pixel brightness (0–255). Defaults to current global brightness.
Example:
# Draw a smiley face
led_set_frame([
0b00111100,
0b01000010,
0b10100101,
0b10000001,
0b10100101,
0b10011001,
0b01000010,
0b00111100,
])
# Draw an X with explicit brightness
led_set_frame([
0b10000001,
0b01000010,
0b00100100,
0b00011000,
0b00011000,
0b00100100,
0b01000010,
0b10000001,
], 50) # dim
led_start_animation(name, [interval_ms])
Start a builtin animation on the matrix.
name: Animation name string (see Animation Constants above).interval_ms(optional): Frame interval in milliseconds. Default 120ms.- Returns
Trueon success.
Example:
led_start_animation(ANIM_PULSE_HEART)
import time
time.sleep(5)
led_stop_animation()
led_stop_animation()
Stop the currently running animation.
led_override_begin()
Pause the saved ambient LED mode so a foreground app can draw on the matrix. Call this before direct LED drawing when you want to temporarily override the LED app.
led_override_end()
Release a foreground LED override and restore the saved ambient LED mode.
Call this from cleanup paths after led_override_begin().
Matrix App Host
Register a Python callback that the firmware calls periodically to render on the 8×8 LED matrix in the background. This lets apps run LED animations that continue while the main Python loop handles OLED and input, or even while no Python code is running (the callback fires from the service pump).
matrix_app_start(callback, [interval_ms], [brightness])
Register a callable to be invoked every interval_ms milliseconds.
callback: A callable that accepts one argument — the currentmillis()timestamp. PassNoneto clear the callback.interval_ms(optional): Tick interval in milliseconds. Minimum 16 ms. Defaults to the firmware's ambient interval.brightness(optional): LED brightness (0–255). Defaults to the firmware's ambient brightness.
Example:
frame = 0
def led_tick(now_ms):
global frame
led_clear()
col = frame % 8
for row in range(8):
led_set_pixel(col, row, 80)
frame += 1
matrix_app_start(led_tick, 100, 40)
matrix_app_set_speed(interval_ms)
Change the tick interval for the active callback.
interval_ms: New interval in milliseconds (minimum 16 ms).- Returns the clamped interval.
matrix_app_set_brightness(brightness)
Change the LED brightness for the active callback.
brightness: New brightness (0–255).- Returns the clamped brightness.
matrix_app_stop()
Unregister the callback and restore default ambient LED mode.
matrix_app_active()
Check if a background callback is currently registered.
- Returns
Trueif a callback is active.
matrix_app_info()
Return a diagnostic tuple with the current matrix app state.
- Returns
(active, saved, interval_ms, brightness, overridden, invocations).
App Manifest
The firmware's AppRegistry discovers folder apps under /apps/<slug>/ at
boot (and on demand via rescan_apps()) and exposes them on the main grid
menu. Each app's tile is decorated from a small set of optional top-level
dunder assignments inside main.py. The scanner reads the first ~2 KB of
the file as text and parses the assignments without executing any code, so
your dunders must sit at the top of the file with simple string literals.
Dunders
Place these at the top of /apps/<slug>/main.py:
| Dunder | Type | Default | Purpose |
|---|---|---|---|
__title__ |
str (≤ 19 chars) |
slug, title-cased | Main-menu tile label |
__description__ |
str (≤ 63 chars) |
empty | Detail panel text |
__icon__ |
str (path or inline tuple) |
tries icon.py |
12×12 home-screen icon |
__matrix_title__ |
str (≤ 19 chars) |
__title__ |
MATRIX APPS picker label (only when matrix.py is present) |
__order__ |
int (signed) |
10000 + discovery index |
Sort key on the main grid; lower = earlier |
"""My Game — Tamagotchi-style desk pet."""
__title__ = "My Game"
__description__ = "A tiny pet that lives in your pocket."
__icon__ = "icon.py"
__matrix_title__ = "Pet"
# ... rest of main.py ...
The slug is the folder name. It must match [A-Za-z0-9_-]+ and may not
start with a .; anything else is silently skipped by the scanner. The
registry holds at most 32 dynamic apps per badge.
icon.py — Home-Screen Icon
Path resolution for __icon__:
- Bare filename (
"icon.py") →/apps/<slug>/icon.py - Absolute path (
"/apps/foo/icon.py") → used as-is - Inline tuple (
"(0xFF, 0x..., )") → parsed directly
The icon file just needs a top-level DATA = (...) tuple of 24 bytes
arranged as a 12×12 packed XBM (2 bytes per row × 12 rows). Bit 0 of each
byte is the leftmost pixel — same byte order as U8G2's drawXBM. The high
4 bits of every odd byte are unused (the row is only 12 wide). WIDTH /
HEIGHT are decorative; the firmware always reads 12×12.
"""My Game icon."""
WIDTH = 12
HEIGHT = 12
# Two bytes per row (low = cols 0..7, high = cols 8..11). Binary
# literals so the dot pattern is visible in the source. XBM is
# LSB-first, so reading the literal left-to-right gives the mirrored
# row — that's accepted, the bit values are still correct.
DATA = (
0b01110111, 0b00000111,
0b01110111, 0b00000111,
0b00000000, 0b00000000,
0b01100000, 0b00000000,
0b01100000, 0b00000000,
0b11111100, 0b00000011,
0b00000000, 0b00000000,
0b00111110, 0b00000000,
0b00100000, 0b00000000,
0b11100000, 0b00000011,
0b00000000, 0b00000010,
0b11000000, 0b00000011,
)
If __icon__ is omitted, the registry still tries
/apps/<slug>/icon.py opportunistically. If that file is missing or
unparsable the tile falls back to the generic apps glyph.
matrix.py — Persistent Matrix App
A sibling matrix.py next to main.py enables the app's slot in the
MATRIX APPS picker (main menu → MATRIX APPS). Selecting it persists the
slug to /led_state.json and re-sources matrix.py once on every boot
that selection survives. The script's only job is to register a callback
via matrix_app_start and return — do not spin a
main loop.
"""Drifting-dot ambient."""
__matrix_title__ = "Drift"
import badge
_phase = 0
def _tick(now_ms):
global _phase
_phase = (_phase + 1) & 7
frame = [0] * 8
frame[7] = 0x80 >> _phase
badge.led_set_frame(frame)
badge.matrix_app_start(_tick, 250, 24)
Same constraints as any other matrix_app_start callback: it runs from the
firmware service pump, so keep ticks fast and don't block. The firmware
tears down the previous callback for you when the user picks a different
matrix app or any built-in mode (Sparkle, Off, etc.) — there's no need to
call matrix_app_stop() from your script when switching modes.
rescan_apps()
Force the registry to re-scan /apps/ after editing manifests, without
rebooting:
import badge
badge.rescan_apps()
This rebuilds the main-menu grid in place. Existing screens stay open.
Tile Order (__order__)
The main grid is rendered in stable-sort order by a signed int16 key.
Three layers feed the key, each overriding the previous one:
- Defaults. Curated tiles use
10 × array index(withSETTINGSpinned to30000so it stays at the back). Dynamic apps default to10000 + discovery index. - App manifest.
__order__ = 50at the top ofmain.pyclaims a specific slot. Any signed integer literal works. - User override. The manual reorder UI (Settings → Menu → Reorder)
writes per-label overrides into the
menu_orderNVS namespace; those override both of the above.
Ties resolve by insertion order — duplicate keys keep the order
established by the previous layer. Negative keys land before all
curated tiles; large keys land near SETTINGS.
__order__ = -10 # before BOOP / CONTACTS / …
__order__ = 25 # between MAP (30) and SCHEDULE (40)
__order__ = 9999 # nearly last
Manual Reorder Screen
Players can rearrange the grid themselves at runtime:
Settings → Menu → Reorder
| Button | Action |
|---|---|
| Joystick Y | Move cursor up/down |
X |
Pick up / drop a row |
A (confirm) |
Save and rebuild |
B (back) |
Cancel without saving |
While picked up, dragging Y swaps the row through the list in real
time. Saving writes the new positions into NVS (menu_order
namespace, FNV-1a-hashed labels as keys). Settings → Menu → Reset
Order wipes the namespace and returns every tile to its default
order.
IMU (Accelerometer)
LIS2DH12 3-axis accelerometer for tilt sensing and motion detection.
imu_ready()
Check if the IMU is initialized and taking readings.
- Returns
Trueif the IMU is ready.
imu_tilt_x()
Read the smoothed X-axis tilt.
- Returns a float in milli-g (mG). Typical range ±1000 mG.
imu_tilt_y()
Read the smoothed Y-axis tilt.
- Returns a float in milli-g (mG). Typical range ±1000 mG.
imu_accel_z()
Read the Z-axis acceleration.
- Returns a float in milli-g (mG). ~1000 mG when stationary (1g gravity).
imu_face_down()
Check if the badge is face-down.
- Returns
Trueif the badge is face-down (Z-axis below threshold).
imu_motion()
Check for a motion event. Consumes the event on read — calling again
returns False until new motion is detected.
- Returns
Trueif motion was detected since the last call.
Example:
import time
while True:
if imu_motion():
oled_clear()
oled_println("Motion!")
haptic_pulse()
x = imu_tilt_x()
y = imu_tilt_y()
# Map tilt to LED matrix pixel
px = int((x + 1000) / 250)
py = int((y + 1000) / 250)
px = max(0, min(7, px))
py = max(0, min(7, py))
led_clear()
led_set_pixel(px, py, 100)
time.sleep(0.05)
Haptics
Vibration motor with PWM control and audible coil tone support. The motor can produce haptic feedback pulses or, at very low duty cycles, audible tones from coil vibration.
haptic_pulse([strength], [duration_ms], [freq_hz])
Fire a haptic vibration pulse. All parameters are optional — omitted values use the configured defaults (strength ~155, duration ~35ms, frequency ~80Hz).
strength(optional): Motor intensity (0–255).duration_ms(optional): Pulse duration in milliseconds.freq_hz(optional): PWM carrier frequency in Hz.
Example:
haptic_pulse() # Default pulse
haptic_pulse(200) # Stronger
haptic_pulse(100, 50) # Medium strength, 50ms
haptic_pulse(255, 100, 150) # Full strength, 100ms, 150Hz carrier
haptic_strength([value])
Get or set the default motor strength used by haptic_pulse().
value(optional): New strength (0–255). If omitted, just returns current.- Returns the current strength (0–255).
Example:
print(haptic_strength()) # Read current
haptic_strength(200) # Set to 200
haptic_off()
Immediately stop the motor and cancel any active pulse.
tone(freq_hz, [duration_ms], [duty])
Play an audible tone from the motor coil. At very low duty cycles (~30/255), the coil doesn't spin but vibrates audibly at the PWM frequency.
freq_hz: Tone frequency in Hz.duration_ms(optional): Duration in milliseconds.0or omitted = play untilno_tone().duty(optional): Duty cycle (0–255). Default 30.
Example:
import time
# Play a scale
for freq in [262, 294, 330, 349, 392, 440, 494, 523]:
tone(freq, 200)
time.sleep(0.25)
# Continuous tone until stopped
tone(440)
time.sleep(2)
no_tone()
no_tone()
Stop the currently playing tone.
tone_playing()
Check if a tone is currently playing.
- Returns
Trueif a tone is active.
IR Send/Receive
Infrared communication over a modified NEC protocol using the onboard IR LED (TX) and TSOP receiver (RX). The RMT-driven encoder automatically prepends an NEC leader, packs each payload word, appends a CRC32, and emits a trailing pulse — MicroPython just picks the payload.
Two payload shapes are available:
- Classic 1-byte/1-byte frames via
ir_send(addr, cmd)/ir_read(). A single 32-bit NEC word(~addr, addr, ~cmd, cmd), ~110 ms on wire. - Multi-word frames via
ir_send_words(words)/ir_read_words(). 1–64 raw 32-bit words (up to 256 bytes of payload) with an appended CRC. Each data word adds ~54 ms, so a 3-word frame ≈ 230 ms on wire.
The Boop screen and MicroPython share the same RMT hardware; ir_start()
brings the radio up for Python use and waits for it to be ready before
returning, so the first ir_send*() after ir_start() will not race.
ir_send(addr, cmd)
Transmit a single classic NEC frame.
addr: NEC address byte (0–255).cmd: NEC command byte (0–255).
Blocks briefly while the RMT hardware streams the frame. Returns 0 on
success; raises OSError if the IR hardware is down (e.g. called before
ir_start()).
ir_start()
Bring the IR hardware up and start receiving in Python mode. Incoming frames
are queued in an 8-slot ring buffer for retrieval with ir_read() or
ir_read_words(). ir_start() blocks up to ~500 ms while Core 0 powers the
RMT channel, and drains any stale frames left over from the Boop screen.
ir_stop()
Stop Python IR RX and drop any queued frames. Does not power down the hardware — the Boop screen may still use it.
ir_available()
Check if at least one classic NEC frame is waiting in the RX queue.
- Returns
Trueifir_read()would return a frame.
ir_read()
Pop one classic NEC frame from the queue.
- Returns
(addr, cmd)if a frame is available, orNoneif the queue is empty.
Example:
import time
ir_start()
oled_println("Listening for IR...")
while True:
if ir_available():
frame = ir_read()
if frame:
addr, cmd = frame
oled_clear()
oled_println("IR: " + hex(addr) + " " + hex(cmd))
haptic_pulse()
time.sleep(0.05)
ir_send_words(words)
Transmit a multi-word NEC frame. The encoder automatically emits a leader, then each word LSB-first, then a CRC32 over the payload.
words: A list, tuple or other sequence of 1–64 integers. Each element is converted to an unsigned 32-bit value.- Raises
ValueErrorif the sequence is empty or longer than 64 words. - Raises
OSError(1)if the IR hardware is not up (e.g. beforeir_start()).
Example:
ir_start()
ir_send_words([0xB0, 0xDEADBEEF, 0x12345678])
ir_read_words()
Read one received multi-word NEC frame.
- Returns a
tupleof up to 64 integers (the payload words, CRC-validated and stripped by the decoder) if a frame is available. - Returns
Noneif the RX queue is empty.
ir_flush()
Drop every pending RX frame (both classic and multi-word queues). Safe to call at any time; a no-op if the IR hardware is down.
ir_tx_power([percent])
Get or set the IR carrier duty cycle. A higher duty drives the IR LED harder and extends range at the cost of current and LED stress.
percent(optional): New duty cycle in the range 1–50 (percent of the 38 kHz carrier period). Call with no argument to just read the current value. Power-on default is 50.- Returns the current duty as an integer percent after the (optional) set.
- Raises
ValueErrorifpercentis outside 1–50.
Example:
ir_start()
print("Default duty:", ir_tx_power()) # 50
ir_tx_power(10) # throttle down for self-loopback
Badge Identity & Boops
my_uuid()
Return this badge's globally unique identifier, derived from the ESP32-S3
eFuse OPTIONAL_UNIQUE_ID (first 6 bytes, hex-encoded).
- Returns a 12-character lowercase hex string, e.g.
"a1b2c3d4e5f6".
boops()
Return the on-flash /boops.json contents as a string. This is the same
document that the Boop screen maintains — each completed boop is appended
with the peer UID, peer name / ticket (if the server backfilled them), and
a status field ("ok", "local", etc.).
- Returns a JSON string. When no boops have been recorded yet, returns
'{"pairings":[]}'so callers can alwaysjson.loads()the result.
Example:
import json
data = json.loads(boops())
for p in data.get("pairings", []):
oled_println(p.get("peer_badge_uid", "?"))
oled_show()
Script Control
exit()
Cleanly stop the currently running MicroPython app by raising SystemExit.
Prefer this over sys.exit() so the host runtime sees the same exception
type the rest of the firmware expects.
Holding all four face buttons for about 1 second also force-exits the running app from outside.
dev(*args) (dev builds only)
Variadic string-argument dispatcher for the firmware test harness. Only
available in builds with BADGE_ENABLE_MP_DEV (e.g. the echo-dev
PlatformIO environment). Each argument is coerced to a string and forwarded
to the C++ runtime. Returns a string result.
Filesystem Access
The badge's VFS is mounted and accessible through the standard os module.
Apps can read and write files for saving state, high scores, or configuration.
import os
os.listdir("/apps")
os.listdir("/")
with open("/apps/my_app/save.json", "w") as f:
f.write('{"score": 42}')
with open("/apps/my_app/save.json", "r") as f:
data = f.read()
Available os operations: listdir, mkdir, remove, rename, stat,
getcwd, chdir, ilistdir, statvfs. Standard open() / read() /
write() / close() work as expected.
Mouse Overlay
Hardware-composited cursor overlay for building GUIs and games. When enabled, a cursor sprite is automatically drawn on top of the OLED framebuffer at every display refresh. The joystick controls cursor position and face buttons generate click events — all handled asynchronously in the service pump so Python code only needs to query position and clicks.
mouse_overlay(enable)
Enable or disable the cursor overlay.
enable:Trueto enable,Falseto disable.
When enabled, the joystick moves the cursor and button presses are captured
as click events (instead of being consumed by button_pressed()).
Example:
mouse_overlay(True)
mouse_set_pos(64, 32)
while True:
oled_clear()
oled_set_cursor(0, 0)
oled_print("x:" + str(mouse_x()) + " y:" + str(mouse_y()))
oled_show()
btn = mouse_clicked()
if btn == BTN_RIGHT:
oled_println("Clicked!")
if btn == BTN_LEFT:
break
time.sleep_ms(30)
mouse_overlay(False)
mouse_set_bitmap(data, w, h)
Set a custom cursor sprite. Format is a packed 1-bit-per-pixel bitmap,
row-major, MSB-first within each byte (the leftmost pixel of a row is
bit 7 of its first byte). Each row is padded to a whole number of bytes, so
ceil(w / 8) * h bytes are read from data.
data:bytesorbytearraycontaining the bitmap.w: Width in pixels (1–32). Values larger than 32 are clamped to 32.h: Height in pixels (1–32). Values larger than 32 are clamped to 32.
The internal cursor buffer is 128 bytes, so any (w, h) with
ceil(w / 8) * h ≤ 128 is accepted. A 32×32 sprite uses the entire buffer.
The hot-spot is automatically set to the sprite's center. The default cursor
is an 8×8 arrow pointer.
Example:
# 8x8 crosshair cursor
crosshair = bytes([
0b00010000,
0b00010000,
0b00010000,
0b11101110,
0b00010000,
0b00010000,
0b00010000,
0b00000000,
])
mouse_set_bitmap(crosshair, 8, 8)
mouse_x()
Returns the current cursor X position (0–127).
mouse_y()
Returns the current cursor Y position (0–63).
mouse_set_pos(x, y)
Warp the cursor to an absolute position.
x: X position (clamped to 0–127).y: Y position (clamped to 0–63).
mouse_clicked()
Read the last click event. Returns the button ID (BTN_RIGHT, BTN_DOWN,
BTN_LEFT, BTN_UP) or -1 if no click is pending. Consumes the event
on read — calling again returns -1 until a new button is pressed.
mouse_set_speed(speed)
Set the cursor movement speed (only affects relative mode).
speed: Pixels per joystick poll at full deflection (1–20). Default is 3.
Example:
mouse_set_speed(5) # faster cursor
mouse_set_speed(1) # very precise
mouse_set_mode(mode)
Switch between absolute and relative positioning.
mode:MOUSE_ABSOLUTE(joystick position = cursor position) orMOUSE_RELATIVE(joystick deflection = cursor velocity). Default isMOUSE_RELATIVE.
In absolute mode the cursor tracks the joystick 1:1 — stick center is
screen center. In relative mode the joystick acts like a mouse — deflect
to move, release to stop. Use mouse_set_speed() and mouse_set_pos() to
tune relative mode.
Example:
mouse_set_mode(MOUSE_RELATIVE)
mouse_set_pos(64, 32)
mouse_set_speed(4)
Constants Reference
Buttons
| Constant | Value | Description |
|---|---|---|
BTN_RIGHT |
0 | Right button |
BTN_DOWN |
1 | Down button |
BTN_LEFT |
2 | Left button |
BTN_UP |
3 | Up button |
BTN_CIRCLE |
0 | PlayStation-style alias for right |
BTN_CROSS |
1 | PlayStation-style alias for down |
BTN_SQUARE |
2 | PlayStation-style alias for left |
BTN_TRIANGLE |
3 | PlayStation-style alias for up |
BTN_CONFIRM |
4 | Semantic confirm/select, follows swap_ok |
BTN_SAVE |
4 | Semantic save/apply, follows swap_ok |
BTN_BACK |
5 | Semantic back/cancel, follows swap_ok |
BTN_PRESETS |
3 | Semantic alias for preset/actions |
LED Matrix Images
| Constant | Value | Description |
|---|---|---|
IMG_SMILEY |
"smiley" |
Smiley face |
IMG_HEART |
"heart" |
Heart shape |
IMG_ARROW_UP |
"arrow_up" |
Upward arrow |
IMG_ARROW_DOWN |
"arrow_down" |
Downward arrow |
IMG_X_MARK |
"x_mark" |
X mark |
IMG_DOT |
"dot" |
Center dot |
LED Matrix Animations
| Constant | Value | Description |
|---|---|---|
ANIM_SPINNER |
"spinner" |
Rotating spinner |
ANIM_BLINK_SMILEY |
"blink_smiley" |
Blinking smiley face |
ANIM_PULSE_HEART |
"pulse_heart" |
Pulsing heart |
Mouse Overlay Modes
| Constant | Value | Description |
|---|---|---|
MOUSE_ABSOLUTE |
1 | Joystick position = cursor position |
MOUSE_RELATIVE |
0 | Joystick deflection = cursor velocity (default) |