| name | badger-app-creator |
| description | Create MicroPython applications for Universe 2025 (Tufty) Badge including display graphics, button handling, and MonaOS app structure. Use when building badge apps, creating interactive displays, or developing MicroPython programs. |
Universe 2025 Badge App Creator
Create well-structured MicroPython applications for the Universe 2025 (Tufty) Badge with MonaOS integration, display graphics, button handling, and proper app architecture.
Important: MonaOS App Structure
Critical: MonaOS apps follow a specific structure. Each app is a directory in /system/apps/ containing:
/system/apps/my_app/
āāā icon.png # 24x24 PNG icon
āāā __init__.py # Entry point with update() function
āāā assets/ # Optional: app assets (auto-added to path)
āāā ...
Required Functions
Your __init__.py must implement:
update() - Required, called every frame by MonaOS:
def update():
pass
init() - Optional, called once when app launches:
def init():
pass
on_exit() - Optional, called when HOME button pressed:
def on_exit():
pass
MonaOS App Template
from badgeware import screen, brushes, shapes, io, PixelFont, Image
app_state = {
"counter": 0,
"color": (255, 255, 255)
}
def init():
"""Called once when app launches"""
screen.font = PixelFont.load("nope.ppf")
try:
with open("/storage/myapp_state.txt", "r") as f:
app_state["counter"] = int(f.read())
except:
pass
print("App initialized!")
def update():
"""Called every frame by MonaOS"""
screen.brush = brushes.color(20, 40, 60)
screen.clear()
screen.brush = brushes.color(255, 255, 255)
screen.text("My App", 10, 10)
screen.text(f"Count: {app_state['counter']}", 10, 30)
if io.BUTTON_A in io.pressed:
app_state["counter"] += 1
if io.BUTTON_B in io.pressed:
app_state["counter"] = 0
def on_exit():
"""Called when returning to MonaOS menu"""
with open("/storage/myapp_state.txt", "w") as f:
f.write(str(app_state["counter"]))
print("App exiting!")
Display API (badgeware)
Import Modules
from badgeware import screen, brushes, shapes, Image, PixelFont, Matrix, io
Screen Drawing (160x120 framebuffer)
The screen is a 160Ć120 RGB framebuffer that MonaOS automatically pixel-doubles to 320Ć240.
Basic Drawing:
screen.brush = brushes.color(r, g, b)
screen.clear()
screen.text("Hello", x, y)
screen.draw(shapes.rectangle(x, y, width, height))
screen.draw(shapes.circle(x, y, radius))
screen.draw(shapes.line(x1, y1, x2, y2))
screen.draw(shapes.arc(x, y, radius, start_angle, end_angle))
screen.draw(shapes.pie(x, y, radius, start_angle, end_angle))
Antialiasing (smooth edges):
screen.antialias = Image.X4
screen.antialias = Image.NONE
No Manual Update Needed: MonaOS automatically updates the display after each update() call.
Shapes Module
Full documentation: https://github.com/badger/home/blob/main/badgerware/shapes.md
Available Shapes:
from badgeware import shapes
shapes.rectangle(x, y, width, height)
shapes.circle(x, y, radius)
shapes.line(x1, y1, x2, y2)
shapes.arc(x, y, radius, start_angle, end_angle)
shapes.pie(x, y, radius, start_angle, end_angle)
shapes.rounded_rectangle(x, y, width, height, radius)
shapes.regular_polygon(x, y, sides, radius)
shapes.squircle(x, y, width, height)
Transformations:
from badgeware import Matrix
rect = shapes.rectangle(-1, -1, 2, 2)
rect.transform = Matrix() \
.translate(80, 60) \
.scale(20, 20) \
.rotate(io.ticks / 100)
screen.draw(rect)
Brushes Module
Full documentation: https://github.com/badger/home/blob/main/badgerware/brushes.md
Solid Colors:
from badgeware import brushes
screen.brush = brushes.color(r, g, b)
screen.brush = brushes.color(255, 0, 0)
screen.brush = brushes.color(0, 255, 0)
screen.brush = brushes.color(0, 0, 255)
screen.brush = brushes.color(255, 255, 255)
screen.brush = brushes.color(0, 0, 0)
Fonts
Full documentation: https://github.com/badger/home/blob/main/PixelFont.md
30 Licensed Pixel Fonts Included:
from badgeware import PixelFont
screen.font = PixelFont.load("nope.ppf")
screen.text("Styled text", x, y)
width = screen.font.measure("text to measure")
screen.font = None
Images & Sprites
Full documentation: https://github.com/badger/home/blob/main/badgerware/Image.md
Loading Images:
from badgeware import Image
img = Image.load("sprite.png")
screen.blit(img, x, y)
screen.scale_blit(img, x, y, width, height)
Sprite Sheets:
from lib import SpriteSheet
sprites = SpriteSheet("assets/mona-sprites.png", 7, 1)
screen.blit(sprites.sprite(0, 0), x, y)
screen.scale_blit(sprites.sprite(3, 0), x, y, 30, 30)
Button Handling (io module)
Full documentation: https://github.com/badger/home/blob/main/badgerware/io.md
Button Constants
from badgeware import io
io.BUTTON_A
io.BUTTON_B
io.BUTTON_C
io.BUTTON_UP
io.BUTTON_DOWN
io.BUTTON_HOME
Button States
Check button states within your update() function:
def update():
if io.BUTTON_A in io.pressed:
print("A was just pressed")
if io.BUTTON_B in io.released:
print("B was just released")
if io.BUTTON_C in io.held:
print("C is being held")
if io.BUTTON_UP in io.changed:
print("UP state changed")
No Debouncing Needed: The io module handles button debouncing automatically.
Menu Navigation Example
menu_items = ["Option 1", "Option 2", "Option 3", "Option 4"]
selected = 0
def update():
global selected
screen.brush = brushes.color(20, 40, 60)
screen.clear()
screen.brush = brushes.color(255, 255, 255)
screen.text("Menu", 10, 5)
y = 30
for i, item in enumerate(menu_items):
if i == selected:
screen.brush = brushes.color(255, 255, 0)
screen.text("> " + item, 10, y)
else:
screen.brush = brushes.color(200, 200, 200)
screen.text(" " + item, 10, y)
y += 20
if io.BUTTON_UP in io.pressed:
selected = (selected - 1) % len(menu_items)
if io.BUTTON_DOWN in io.pressed:
selected = (selected + 1) % len(menu_items)
if io.BUTTON_A in io.pressed:
print(f"Selected: {menu_items[selected]}")
Animation & Timing
Using io.ticks
from badgeware import io
import math
def update():
y = (math.sin(io.ticks / 100) * 30) + 60
angle = io.ticks / 50
rect = shapes.rectangle(-1, -1, 2, 2)
rect.transform = Matrix().translate(80, 60).rotate(angle)
screen.draw(rect)
scale = (math.sin(io.ticks / 60) * 10) + 20
circle = shapes.circle(80, 60, scale)
screen.draw(circle)
State Management
Persistent Storage
Store app data in the writable LittleFS partition at /storage/:
import json
CONFIG_FILE = "/storage/myapp_config.json"
def save_config(data):
"""Save configuration to persistent storage"""
try:
with open(CONFIG_FILE, "w") as f:
json.dump(data, f)
print("Config saved!")
except Exception as e:
print(f"Save failed: {e}")
def load_config():
"""Load configuration from persistent storage"""
try:
with open(CONFIG_FILE, "r") as f:
return json.load(f)
except:
return {
"name": "Badge User",
"theme": "light",
"counter": 0
}
config = {}
def init():
global config
config = load_config()
print(f"Loaded: {config}")
def on_exit():
save_config(config)
State Machine Pattern
class AppState:
MENU = 0
GAME = 1
SETTINGS = 2
GAME_OVER = 3
state = AppState.MENU
game_data = {"score": 0, "level": 1}
def update():
global state
if state == AppState.MENU:
draw_menu()
if io.BUTTON_A in io.pressed:
state = AppState.GAME
elif state == AppState.GAME:
update_game()
draw_game()
if game_data["score"] < 0:
state = AppState.GAME_OVER
elif state == AppState.SETTINGS:
draw_settings()
if io.BUTTON_B in io.pressed:
state = AppState.MENU
elif state == AppState.GAME_OVER:
draw_game_over()
if io.BUTTON_A in io.pressed:
state = AppState.MENU
game_data = {"score": 0, "level": 1}
def draw_menu():
screen.brush = brushes.color(0, 0, 0)
screen.clear()
screen.brush = brushes.color(255, 255, 255)
screen.text("MAIN MENU", 40, 50)
screen.text("Press A to start", 30, 70)
def update_game():
game_data["score"] += 1
def draw_game():
screen.brush = brushes.color(0, 0, 0)
screen.clear()
screen.brush = brushes.color(255, 255, 255)
screen.text(f"Score: {game_data['score']}", 10, 10)
def draw_settings():
pass
def draw_game_over():
screen.brush = brushes.color(0, 0, 0)
screen.clear()
screen.brush = brushes.color(255, 0, 0)
screen.text("GAME OVER", 40, 50)
screen.text(f"Score: {game_data['score']}", 40, 70)
WiFi Integration
Use standard MicroPython network module:
import network
import time
def connect_wifi(ssid, password):
"""Connect to WiFi network"""
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if wlan.isconnected():
print("Already connected:", wlan.ifconfig()[0])
return True
print(f"Connecting to {ssid}...")
wlan.connect(ssid, password)
timeout = 10
while not wlan.isconnected() and timeout > 0:
time.sleep(1)
timeout -= 1
if wlan.isconnected():
print("Connected:", wlan.ifconfig()[0])
return True
else:
print("Connection failed")
return False
def fetch_data(url):
"""Fetch data from URL"""
try:
import urequests
response = urequests.get(url)
data = response.json()
response.close()
return data
except Exception as e:
print(f"Error fetching data: {e}")
return None
def init():
if connect_wifi("MyWiFi", "password123"):
data = fetch_data("https://api.example.com/data")
if data:
print("Got data:", data)
Full WiFi docs: https://docs.micropython.org/en/latest/rp2/quickref.html#wlan
Performance Optimization
Reduce Draw Calls
def update():
for i in range(100):
screen.draw(shapes.rectangle(i, i, 2, 2))
def update():
screen.draw(shapes.rectangle(0, 0, 100, 100))
Cache Computed Values
_cached_sprites = None
def get_sprites():
global _cached_sprites
if _cached_sprites is None:
_cached_sprites = SpriteSheet("sprites.png", 8, 8)
return _cached_sprites
def update():
sprites = get_sprites()
screen.blit(sprites.sprite(0, 0), 10, 10)
Minimize Memory Allocation
def update():
items = [1, 2, 3, 4, 5]
for item in items:
process(item)
items = [1, 2, 3, 4, 5]
def update():
for item in items:
process(item)
Project Structure Best Practices
Simple App (Single File)
my_app/
āāā icon.png
āāā __init__.py
Complex App (Multiple Files)
my_app/
āāā icon.png
āāā __init__.py # Entry point
āāā assets/
āāā sprites.png
āāā font.ppf
āāā config.json
Access assets using relative paths (assets/ is auto-added to sys.path):
from badgeware import Image
sprite = Image.load("assets/sprites.png")
sprite = Image.load("sprites.png")
Error Handling
def update():
"""Update with error handling"""
try:
draw_ui()
handle_input()
except Exception as e:
screen.brush = brushes.color(255, 0, 0)
screen.clear()
screen.brush = brushes.color(255, 255, 255)
screen.text("Error:", 10, 10)
screen.text(str(e)[:30], 10, 30)
import sys
sys.print_exception(e)
Testing & Debugging
Test Locally
mpremote run my_app/__init__.py
REPL Debugging
mpremote
>>> from badgeware import screen, brushes
>>> screen.brush = brushes.color(255, 0, 0)
>>> screen.clear()
Print Debugging
def update():
print(f"State: {state}, Counter: {counter}")
screen.text(f"Debug: {value}", 0, 110)
Official Examples
Study official examples: https://github.com/badger/home/tree/main/badge/apps
Key examples:
- Commits Game: Sprite animations, collision detection
- Snake Game: Grid-based movement, state management
- Menu System: Navigation, app launching
Official API Documentation
Complete Example App
from badgeware import screen, brushes, shapes, io, PixelFont
import json
counter = 0
high_score = 0
def init():
"""Load saved state"""
global counter, high_score
screen.font = PixelFont.load("nope.ppf")
try:
with open("/storage/counter_state.json", "r") as f:
data = json.load(f)
counter = data.get("counter", 0)
high_score = data.get("high_score", 0)
except:
pass
print(f"Counter initialized: {counter}, High: {high_score}")
def update():
"""Update every frame"""
global counter, high_score
screen.brush = brushes.color(20, 40, 60)
screen.clear()
screen.brush = brushes.color(255, 255, 255)
screen.text("COUNTER APP", 30, 10)
screen.text(f"{counter}", 60, 40, scale=3)
screen.text(f"High: {high_score}", 40, 80)
screen.text("A: +1 B: Reset", 20, 105)
if io.BUTTON_A in io.pressed:
counter += 1
if counter > high_score:
high_score = counter
if io.BUTTON_B in io.pressed:
counter = 0
def on_exit():
"""Save state before exit"""
try:
with open("/storage/counter_state.json", "w") as f:
json.dump({
"counter": counter,
"high_score": high_score
}, f)
print("State saved!")
except Exception as e:
print(f"Save failed: {e}")
Next Steps
- See Official Hacks: https://badger.github.io/hacks/
- Explore Badge Hardware: Use
badger-hardware skill
- WiFi & Bluetooth: See MicroPython docs
- Deploy Your App: Use
badger-deploy skill
Happy coding! š¦”