Initial commit
This commit is contained in:
252
skills/badger-2350-dev/SKILL.md
Normal file
252
skills/badger-2350-dev/SKILL.md
Normal file
@@ -0,0 +1,252 @@
|
||||
---
|
||||
name: badger-2350-dev
|
||||
description: Development environment setup and workflow for Universe 2025 (Tufty) Badge with MonaOS. Use when setting up the badge, flashing firmware, debugging, or working with the development toolchain.
|
||||
---
|
||||
|
||||
# Universe 2025 Badge Development
|
||||
|
||||
Help develop, flash, and debug applications for the Universe 2025 (Tufty) Badge and its **MonaOS** launcher system.
|
||||
|
||||
## Important: MonaOS & API
|
||||
|
||||
The Universe 2025 Badge uses:
|
||||
- **MonaOS**: Built-in app launcher that auto-discovers apps in `/system/apps/` directory
|
||||
- **badgeware Module**: Custom API with screen, brushes, shapes, io, Image, PixelFont
|
||||
- **Display**: 160x120 framebuffer (pixel-doubled to 320x240)
|
||||
- **App Structure**: Apps are **directories** containing `__init__.py`, `icon.png`, and optional `assets/`
|
||||
- **Entry Point**: Apps must implement `update()` function called every frame
|
||||
|
||||
When developing MonaOS apps:
|
||||
1. Use the `badgeware` module API
|
||||
2. Create app as directory with `__init__.py` and `icon.png`
|
||||
3. Install to `/system/apps/my_app/` directory
|
||||
4. HOME button exits to menu automatically
|
||||
5. Default menu shows 6 apps - enable pagination for more
|
||||
|
||||
## Board Specifications
|
||||
|
||||
- **Processor**: RP2350 dual-core ARM Cortex-M33 @ 200MHz
|
||||
- **Memory**: 512KB SRAM, 16MB QSPI XiP flash
|
||||
- **Display**: 320x240 full-color IPS (160x120 framebuffer pixel-doubled)
|
||||
- **Connectivity**: 2.4GHz WiFi, Bluetooth 5
|
||||
- **Power**: USB-C charging, 1000mAh rechargeable battery (up to 8 hours runtime)
|
||||
- **Special Features**: IR receiver/transmitter, 4-zone LED backlight
|
||||
- **Buttons**: 5 front-facing (A, B, C, UP, DOWN) + HOME button
|
||||
- **Expansion**: 4 GPIO pins, Qw/ST and SWD ports
|
||||
- **Primary Language**: MicroPython with badgeware module + MonaOS
|
||||
|
||||
## Development Setup
|
||||
|
||||
### 1. Install toolchain
|
||||
|
||||
For MicroPython development:
|
||||
```bash
|
||||
# Install Thonny IDE (recommended for beginners)
|
||||
brew install --cask thonny
|
||||
|
||||
# Or install command-line tools
|
||||
pip install esptool adafruit-ampy mpremote
|
||||
```
|
||||
|
||||
For C++ development:
|
||||
```bash
|
||||
# Install Pico SDK
|
||||
git clone https://github.com/raspberrypi/pico-sdk.git
|
||||
export PICO_SDK_PATH=/path/to/pico-sdk
|
||||
```
|
||||
|
||||
### 2. Connect to the badge
|
||||
|
||||
```bash
|
||||
# List connected devices
|
||||
ls /dev/tty.usb*
|
||||
|
||||
# Connect via serial (MicroPython REPL)
|
||||
screen /dev/tty.usbmodem* 115200
|
||||
# Exit screen: Ctrl+A then K
|
||||
```
|
||||
|
||||
### 3. Flash firmware
|
||||
|
||||
```bash
|
||||
# Put badge in bootloader mode (hold BOOT button, press RESET)
|
||||
|
||||
# Flash MicroPython firmware
|
||||
esptool.py --port /dev/tty.usbmodem* write_flash 0x0 firmware.uf2
|
||||
```
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Test app temporarily (doesn't save)
|
||||
```bash
|
||||
mpremote connect /dev/tty.usbmodem* run my_app/__init__.py
|
||||
```
|
||||
|
||||
### Install MonaOS app using USB Mass Storage Mode
|
||||
|
||||
**⚠️ CRITICAL**: `/system/apps/` is READ-ONLY via mpremote. You MUST use USB Mass Storage Mode to install apps.
|
||||
|
||||
```bash
|
||||
# Step 1: Enter Mass Storage Mode
|
||||
# - Press RESET button TWICE quickly (double-click)
|
||||
# - Badge appears as "BADGER" drive
|
||||
|
||||
# Step 2: Copy app to badge
|
||||
# macOS/Linux:
|
||||
cp -r my_app /Volumes/BADGER/apps/
|
||||
|
||||
# Windows:
|
||||
xcopy my_app D:\apps\my_app\ /E /I
|
||||
|
||||
# Step 3: Exit Mass Storage Mode
|
||||
# - Eject BADGER drive safely
|
||||
diskutil eject /Volumes/BADGER # macOS
|
||||
# - Press RESET once to reboot
|
||||
|
||||
# Your app now appears in MonaOS launcher!
|
||||
```
|
||||
|
||||
**File System Mapping**:
|
||||
- `/Volumes/BADGER/apps/` → `/system/apps/` on badge
|
||||
- `/Volumes/BADGER/assets/` → `/system/assets/` on badge
|
||||
|
||||
### List MonaOS apps (read-only view)
|
||||
```bash
|
||||
mpremote connect /dev/tty.usbmodem* ls /system/apps
|
||||
```
|
||||
|
||||
**⚠️ Note**: Install the paginated menu for unlimited apps (default shows 6):
|
||||
- Download: https://raw.githubusercontent.com/badger/home/refs/heads/main/badge/apps/menu/__init__.py
|
||||
- Replace `/Volumes/BADGER/apps/menu/__init__.py` in Mass Storage Mode
|
||||
|
||||
### List files in writable storage
|
||||
```bash
|
||||
mpremote connect /dev/tty.usbmodem* ls /storage
|
||||
```
|
||||
|
||||
### Download file from badge
|
||||
```bash
|
||||
mpremote connect /dev/tty.usbmodem* cp :/system/apps/my_app/__init__.py local_backup.py
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
MonaOS app structure on your computer:
|
||||
```
|
||||
my_app/ # MonaOS app directory
|
||||
├── __init__.py # Entry point with update() function (required)
|
||||
├── icon.png # 24x24 PNG icon for launcher (required)
|
||||
├── assets/ # Optional: app resources (auto-added to path)
|
||||
│ ├── sprites.png
|
||||
│ ├── font.ppf
|
||||
│ └── data.json
|
||||
└── README.md # Optional: app documentation
|
||||
```
|
||||
|
||||
Multiple apps in development:
|
||||
```
|
||||
badge-project/
|
||||
├── my_app/ # First MonaOS app
|
||||
│ ├── __init__.py
|
||||
│ └── icon.png
|
||||
├── game_app/ # Second MonaOS app
|
||||
│ ├── __init__.py
|
||||
│ ├── icon.png
|
||||
│ └── assets/
|
||||
│ └── sprites.png
|
||||
├── requirements.txt # Python tools
|
||||
└── deploy.sh # Deployment script
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
### Check badge logs
|
||||
```python
|
||||
# In REPL
|
||||
import sys
|
||||
sys.print_exception(e) # Print full exception traceback
|
||||
```
|
||||
|
||||
### Test display
|
||||
```python
|
||||
from badgeware import screen, display, brushes
|
||||
|
||||
# Clear with black
|
||||
screen.brush = brushes.color(0, 0, 0)
|
||||
screen.clear()
|
||||
|
||||
# White text
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.text("Hello Badge!", 10, 10, 2)
|
||||
display.update()
|
||||
```
|
||||
|
||||
### Test WiFi
|
||||
```python
|
||||
import network
|
||||
wlan = network.WLAN(network.STA_IF)
|
||||
wlan.active(True)
|
||||
wlan.connect('SSID', 'password')
|
||||
print(wlan.isconnected())
|
||||
print(wlan.ifconfig()) # IP address info
|
||||
```
|
||||
|
||||
## Power Management
|
||||
|
||||
```python
|
||||
import machine
|
||||
import badgeware
|
||||
|
||||
# Check battery level
|
||||
battery = badgeware.get_battery_level()
|
||||
print(f"Battery: {battery}%")
|
||||
|
||||
# Check if USB connected
|
||||
usb = badgeware.get_usb_connected()
|
||||
print(f"USB: {usb}")
|
||||
|
||||
# Light sleep (for delays)
|
||||
machine.lightsleep(1000) # Sleep 1 second
|
||||
|
||||
# Deep sleep (wake on button press - saves significant power)
|
||||
machine.deepsleep()
|
||||
```
|
||||
|
||||
## Tips for MonaOS Apps
|
||||
|
||||
- MonaOS apps use `update()` function called every frame
|
||||
- Optimize `update()` - it runs continuously
|
||||
- Use `io.ticks` for animations instead of time.time()
|
||||
- Minimize allocations in `update()` to reduce GC pauses
|
||||
- Use `try/except` blocks to prevent crashes
|
||||
- Test with USB power first, then battery
|
||||
- Apps automatically return to menu when HOME button pressed
|
||||
|
||||
## Common Issues
|
||||
|
||||
**Badge not detected**: Check USB cable, try different port, press RESET button
|
||||
|
||||
**Out of memory**: Reduce allocations in `update()`, use generators, call `gc.collect()`, free variables with `del`
|
||||
|
||||
**Display not updating**: MonaOS automatically updates after `update()` returns - no manual update needed
|
||||
|
||||
**App not in menu**: Check uploaded to `/system/apps/my_app/`, verify icon.png exists, may need pagination: https://badger.github.io/hack/menu-pagination/
|
||||
|
||||
**WiFi connection fails**: Check credentials, verify 2.4GHz band, restart badge
|
||||
|
||||
## Resources
|
||||
|
||||
### Official Badge Resources
|
||||
- **Getting Started**: https://badger.github.io/get-started/ - Overview and setup
|
||||
- **About Badge**: https://badger.github.io/about-badge/ - Hardware specifications
|
||||
- **Hacks**: https://badger.github.io/hacks/ - Beginner to advanced customization tutorials
|
||||
- **Apps**: https://badger.github.io/apps/ - Loadable MonaOS apps (Commits, Snake)
|
||||
- **Source Code**: https://github.com/badger/home/tree/main/badgerware - Official MonaOS app code and API docs
|
||||
|
||||
### API Documentation
|
||||
- **badgeware modules**: https://github.com/badger/home/tree/main/badgerware - shapes, brushes, io, Image, PixelFont, Matrix
|
||||
|
||||
### Development Resources
|
||||
- **MicroPython docs**: https://docs.micropython.org/
|
||||
- **WiFi/Network**: https://docs.micropython.org/en/latest/rp2/quickref.html#wlan
|
||||
- **Community**: Badger GitHub discussions
|
||||
774
skills/badger-app-creator/SKILL.md
Normal file
774
skills/badger-app-creator/SKILL.md
Normal file
@@ -0,0 +1,774 @@
|
||||
---
|
||||
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:
|
||||
```python
|
||||
def update():
|
||||
# Called every frame
|
||||
# Draw your UI, handle input, update state
|
||||
pass
|
||||
```
|
||||
|
||||
**`init()`** - Optional, called once when app launches:
|
||||
```python
|
||||
def init():
|
||||
# Initialize app state, load resources
|
||||
pass
|
||||
```
|
||||
|
||||
**`on_exit()`** - Optional, called when HOME button pressed:
|
||||
```python
|
||||
def on_exit():
|
||||
# Save state, cleanup resources
|
||||
pass
|
||||
```
|
||||
|
||||
## MonaOS App Template
|
||||
|
||||
```python
|
||||
# __init__.py - MonaOS app template
|
||||
from badgeware import screen, brushes, shapes, io, PixelFont, Image
|
||||
|
||||
# App state
|
||||
app_state = {
|
||||
"counter": 0,
|
||||
"color": (255, 255, 255)
|
||||
}
|
||||
|
||||
def init():
|
||||
"""Called once when app launches"""
|
||||
# Load font
|
||||
screen.font = PixelFont.load("nope.ppf")
|
||||
|
||||
# Load saved state if exists
|
||||
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"""
|
||||
# Clear screen
|
||||
screen.brush = brushes.color(20, 40, 60)
|
||||
screen.clear()
|
||||
|
||||
# Draw UI
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.text("My App", 10, 10)
|
||||
screen.text(f"Count: {app_state['counter']}", 10, 30)
|
||||
|
||||
# Handle buttons (checked every frame)
|
||||
if io.BUTTON_A in io.pressed:
|
||||
app_state["counter"] += 1
|
||||
|
||||
if io.BUTTON_B in io.pressed:
|
||||
app_state["counter"] = 0
|
||||
|
||||
# HOME button exits automatically
|
||||
|
||||
def on_exit():
|
||||
"""Called when returning to MonaOS menu"""
|
||||
# Save state
|
||||
with open("/storage/myapp_state.txt", "w") as f:
|
||||
f.write(str(app_state["counter"]))
|
||||
|
||||
print("App exiting!")
|
||||
```
|
||||
|
||||
## Display API (badgeware)
|
||||
|
||||
### Import Modules
|
||||
|
||||
```python
|
||||
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**:
|
||||
```python
|
||||
# Set brush color (RGB 0-255)
|
||||
screen.brush = brushes.color(r, g, b)
|
||||
|
||||
# Clear screen
|
||||
screen.clear()
|
||||
|
||||
# Draw text
|
||||
screen.text("Hello", x, y)
|
||||
|
||||
# Draw shapes
|
||||
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):
|
||||
```python
|
||||
screen.antialias = Image.X4 # Enable 4x antialiasing
|
||||
screen.antialias = Image.NONE # Disable
|
||||
```
|
||||
|
||||
**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**:
|
||||
```python
|
||||
from badgeware import shapes
|
||||
|
||||
# Rectangle
|
||||
shapes.rectangle(x, y, width, height)
|
||||
|
||||
# Circle
|
||||
shapes.circle(x, y, radius)
|
||||
|
||||
# Line
|
||||
shapes.line(x1, y1, x2, y2)
|
||||
|
||||
# Arc (portion of circle outline)
|
||||
shapes.arc(x, y, radius, start_angle, end_angle)
|
||||
|
||||
# Pie (filled circle segment)
|
||||
shapes.pie(x, y, radius, start_angle, end_angle)
|
||||
|
||||
# Rounded rectangle
|
||||
shapes.rounded_rectangle(x, y, width, height, radius)
|
||||
|
||||
# Regular polygon (pentagon, hexagon, etc.)
|
||||
shapes.regular_polygon(x, y, sides, radius)
|
||||
|
||||
# Squircle (smooth rectangle-circle hybrid)
|
||||
shapes.squircle(x, y, width, height)
|
||||
```
|
||||
|
||||
**Transformations**:
|
||||
```python
|
||||
from badgeware import Matrix
|
||||
|
||||
# Create shape
|
||||
rect = shapes.rectangle(-1, -1, 2, 2)
|
||||
|
||||
# Apply transformation
|
||||
rect.transform = Matrix() \
|
||||
.translate(80, 60) \ # Move to center
|
||||
.scale(20, 20) \ # Scale up
|
||||
.rotate(io.ticks / 100) # Animated rotation
|
||||
|
||||
screen.draw(rect)
|
||||
```
|
||||
|
||||
### Brushes Module
|
||||
|
||||
Full documentation: https://github.com/badger/home/blob/main/badgerware/brushes.md
|
||||
|
||||
**Solid Colors**:
|
||||
```python
|
||||
from badgeware import brushes
|
||||
|
||||
# RGB color (0-255 per channel)
|
||||
screen.brush = brushes.color(r, g, b)
|
||||
|
||||
# Examples
|
||||
screen.brush = brushes.color(255, 0, 0) # Red
|
||||
screen.brush = brushes.color(0, 255, 0) # Green
|
||||
screen.brush = brushes.color(0, 0, 255) # Blue
|
||||
screen.brush = brushes.color(255, 255, 255) # White
|
||||
screen.brush = brushes.color(0, 0, 0) # Black
|
||||
```
|
||||
|
||||
### Fonts
|
||||
|
||||
Full documentation: https://github.com/badger/home/blob/main/PixelFont.md
|
||||
|
||||
**30 Licensed Pixel Fonts Included**:
|
||||
```python
|
||||
from badgeware import PixelFont
|
||||
|
||||
# Load font
|
||||
screen.font = PixelFont.load("nope.ppf")
|
||||
|
||||
# Draw text with loaded font
|
||||
screen.text("Styled text", x, y)
|
||||
|
||||
# Measure text width
|
||||
width = screen.font.measure("text to measure")
|
||||
|
||||
# Reset to default font
|
||||
screen.font = None
|
||||
```
|
||||
|
||||
### Images & Sprites
|
||||
|
||||
Full documentation: https://github.com/badger/home/blob/main/badgerware/Image.md
|
||||
|
||||
**Loading Images**:
|
||||
```python
|
||||
from badgeware import Image
|
||||
|
||||
# Load PNG image
|
||||
img = Image.load("sprite.png")
|
||||
|
||||
# Blit to screen
|
||||
screen.blit(img, x, y)
|
||||
|
||||
# Scaled blit
|
||||
screen.scale_blit(img, x, y, width, height)
|
||||
```
|
||||
|
||||
**Sprite Sheets**:
|
||||
```python
|
||||
# Using SpriteSheet helper (from examples)
|
||||
from lib import SpriteSheet
|
||||
|
||||
# Load sprite sheet (7 columns, 1 row)
|
||||
sprites = SpriteSheet("assets/mona-sprites.png", 7, 1)
|
||||
|
||||
# Blit specific sprite (column 0, row 0)
|
||||
screen.blit(sprites.sprite(0, 0), x, y)
|
||||
|
||||
# Scaled sprite
|
||||
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
|
||||
|
||||
```python
|
||||
from badgeware import io
|
||||
|
||||
# Available buttons
|
||||
io.BUTTON_A # Left button
|
||||
io.BUTTON_B # Middle button
|
||||
io.BUTTON_C # Right button
|
||||
io.BUTTON_UP # Up button
|
||||
io.BUTTON_DOWN # Down button
|
||||
io.BUTTON_HOME # HOME button (exits to MonaOS)
|
||||
```
|
||||
|
||||
### Button States
|
||||
|
||||
Check button states within your `update()` function:
|
||||
|
||||
```python
|
||||
def update():
|
||||
# Button just pressed this frame
|
||||
if io.BUTTON_A in io.pressed:
|
||||
print("A was just pressed")
|
||||
|
||||
# Button just released this frame
|
||||
if io.BUTTON_B in io.released:
|
||||
print("B was just released")
|
||||
|
||||
# Button currently held down
|
||||
if io.BUTTON_C in io.held:
|
||||
print("C is being held")
|
||||
|
||||
# Button state changed this frame (pressed or released)
|
||||
if io.BUTTON_UP in io.changed:
|
||||
print("UP state changed")
|
||||
```
|
||||
|
||||
**No Debouncing Needed**: The io module handles button debouncing automatically.
|
||||
|
||||
### Menu Navigation Example
|
||||
|
||||
```python
|
||||
menu_items = ["Option 1", "Option 2", "Option 3", "Option 4"]
|
||||
selected = 0
|
||||
|
||||
def update():
|
||||
global selected
|
||||
|
||||
# Clear screen
|
||||
screen.brush = brushes.color(20, 40, 60)
|
||||
screen.clear()
|
||||
|
||||
# Draw title
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.text("Menu", 10, 5)
|
||||
|
||||
# Draw menu items
|
||||
y = 30
|
||||
for i, item in enumerate(menu_items):
|
||||
if i == selected:
|
||||
# Highlight selected item
|
||||
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
|
||||
|
||||
# Handle navigation
|
||||
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
|
||||
|
||||
```python
|
||||
from badgeware import io
|
||||
import math
|
||||
|
||||
def update():
|
||||
# io.ticks increments every frame
|
||||
# Use for smooth animations
|
||||
|
||||
# Oscillating value
|
||||
y = (math.sin(io.ticks / 100) * 30) + 60
|
||||
|
||||
# Rotating shape
|
||||
angle = io.ticks / 50
|
||||
rect = shapes.rectangle(-1, -1, 2, 2)
|
||||
rect.transform = Matrix().translate(80, 60).rotate(angle)
|
||||
screen.draw(rect)
|
||||
|
||||
# Pulsing size
|
||||
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/`:
|
||||
|
||||
```python
|
||||
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 defaults if file doesn't exist
|
||||
return {
|
||||
"name": "Badge User",
|
||||
"theme": "light",
|
||||
"counter": 0
|
||||
}
|
||||
|
||||
# Usage in app
|
||||
config = {}
|
||||
|
||||
def init():
|
||||
global config
|
||||
config = load_config()
|
||||
print(f"Loaded: {config}")
|
||||
|
||||
def on_exit():
|
||||
save_config(config)
|
||||
```
|
||||
|
||||
### State Machine Pattern
|
||||
|
||||
```python
|
||||
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 logic
|
||||
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():
|
||||
# Settings UI
|
||||
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:
|
||||
|
||||
```python
|
||||
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)
|
||||
|
||||
# Wait for connection (with timeout)
|
||||
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
|
||||
|
||||
# Usage in app
|
||||
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
|
||||
|
||||
```python
|
||||
# Bad - many individual draws
|
||||
def update():
|
||||
for i in range(100):
|
||||
screen.draw(shapes.rectangle(i, i, 2, 2))
|
||||
|
||||
# Better - batch or optimize
|
||||
def update():
|
||||
# Draw fewer, larger shapes
|
||||
screen.draw(shapes.rectangle(0, 0, 100, 100))
|
||||
```
|
||||
|
||||
### Cache Computed Values
|
||||
|
||||
```python
|
||||
# Cache expensive calculations
|
||||
_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() # Fast after first call
|
||||
screen.blit(sprites.sprite(0, 0), 10, 10)
|
||||
```
|
||||
|
||||
### Minimize Memory Allocation
|
||||
|
||||
```python
|
||||
# Bad - creates new lists every frame
|
||||
def update():
|
||||
items = [1, 2, 3, 4, 5] # Don't do this in update()
|
||||
for item in items:
|
||||
process(item)
|
||||
|
||||
# Good - create once, reuse
|
||||
items = [1, 2, 3, 4, 5] # Module level
|
||||
|
||||
def update():
|
||||
for item in items: # Reuse existing list
|
||||
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):
|
||||
|
||||
```python
|
||||
# In __init__.py
|
||||
from badgeware import Image
|
||||
|
||||
# Load from assets/
|
||||
sprite = Image.load("assets/sprites.png")
|
||||
|
||||
# Or if assets/ in path:
|
||||
sprite = Image.load("sprites.png")
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```python
|
||||
def update():
|
||||
"""Update with error handling"""
|
||||
try:
|
||||
# Your update code
|
||||
draw_ui()
|
||||
handle_input()
|
||||
except Exception as e:
|
||||
# Show error on screen
|
||||
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)
|
||||
|
||||
# Log to console for debugging
|
||||
import sys
|
||||
sys.print_exception(e)
|
||||
```
|
||||
|
||||
## Testing & Debugging
|
||||
|
||||
### Test Locally
|
||||
|
||||
```bash
|
||||
# Run app temporarily without installing
|
||||
mpremote run my_app/__init__.py
|
||||
```
|
||||
|
||||
### REPL Debugging
|
||||
|
||||
```bash
|
||||
# Connect to REPL
|
||||
mpremote
|
||||
|
||||
# Test imports
|
||||
>>> from badgeware import screen, brushes
|
||||
>>> screen.brush = brushes.color(255, 0, 0)
|
||||
>>> screen.clear()
|
||||
```
|
||||
|
||||
### Print Debugging
|
||||
|
||||
```python
|
||||
def update():
|
||||
# Print statements appear in REPL/serial console
|
||||
print(f"State: {state}, Counter: {counter}")
|
||||
|
||||
# Draw debug info on screen
|
||||
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
|
||||
|
||||
- **Image class**: https://github.com/badger/home/blob/main/badgerware/Image.md
|
||||
- **shapes module**: https://github.com/badger/home/blob/main/badgerware/shapes.md
|
||||
- **brushes module**: https://github.com/badger/home/blob/main/badgerware/brushes.md
|
||||
- **PixelFont class**: https://github.com/badger/home/blob/main/PixelFont.md
|
||||
- **Matrix class**: https://github.com/badger/home/blob/main/Matrix.md
|
||||
- **io module**: https://github.com/badger/home/blob/main/badgerware/io.md
|
||||
|
||||
## Complete Example App
|
||||
|
||||
```python
|
||||
# __init__.py - Complete counter app with persistence
|
||||
from badgeware import screen, brushes, shapes, io, PixelFont
|
||||
import json
|
||||
|
||||
# State
|
||||
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
|
||||
|
||||
# Clear screen
|
||||
screen.brush = brushes.color(20, 40, 60)
|
||||
screen.clear()
|
||||
|
||||
# Draw title
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.text("COUNTER APP", 30, 10)
|
||||
|
||||
# Draw counter (large)
|
||||
screen.text(f"{counter}", 60, 40, scale=3)
|
||||
|
||||
# Draw high score
|
||||
screen.text(f"High: {high_score}", 40, 80)
|
||||
|
||||
# Draw instructions
|
||||
screen.text("A: +1 B: Reset", 20, 105)
|
||||
|
||||
# Handle buttons
|
||||
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! 🦡
|
||||
809
skills/badger-deploy/SKILL.md
Normal file
809
skills/badger-deploy/SKILL.md
Normal file
@@ -0,0 +1,809 @@
|
||||
---
|
||||
name: badger-deploy
|
||||
description: Deployment workflows, file management, and project organization for Universe 2025 (Tufty) Badge. Use when deploying apps to MonaOS, managing files on device, syncing projects, organizing code, or setting up deployment pipelines.
|
||||
---
|
||||
|
||||
# Universe 2025 Badge Deployment and File Management
|
||||
|
||||
Efficient workflows for deploying applications to **MonaOS**, managing files, and organizing projects on the Universe 2025 (Tufty) Badge.
|
||||
|
||||
## Deploying to MonaOS
|
||||
|
||||
MonaOS apps are **directories** in `/system/apps/`, not single files. Each app directory must contain:
|
||||
- `__init__.py` - Entry point with `update()` function
|
||||
- `icon.png` - 24x24 PNG icon for the launcher
|
||||
- `assets/` - Optional directory for app resources
|
||||
|
||||
### ⚠️ CRITICAL: USB Mass Storage Mode Required
|
||||
|
||||
**The `/system/apps/` directory is READ-ONLY via mpremote.** You MUST use USB Mass Storage Mode to install, update, or delete apps.
|
||||
|
||||
```bash
|
||||
# Step 1: Enter USB Mass Storage Mode
|
||||
# - Connect badge via USB-C
|
||||
# - Press RESET button TWICE quickly (double-click)
|
||||
# - Badge appears as "BADGER" drive at /Volumes/BADGER (macOS)
|
||||
|
||||
# Step 2: Copy your app to the badge
|
||||
cp -r my_app /Volumes/BADGER/apps/
|
||||
|
||||
# Or manually via Finder:
|
||||
# - Open BADGER drive
|
||||
# - Navigate to apps/ folder
|
||||
# - Drag my_app folder into apps/
|
||||
|
||||
# Step 3: Exit Mass Storage Mode
|
||||
diskutil eject /Volumes/BADGER # Or eject via Finder
|
||||
# Press RESET button once to reboot into MonaOS
|
||||
|
||||
# Your app will now appear in the MonaOS launcher!
|
||||
```
|
||||
|
||||
**File System Mapping**:
|
||||
- `/Volumes/BADGER/apps/` → `/system/apps/` on badge (MonaOS apps)
|
||||
- `/Volumes/BADGER/assets/` → `/system/assets/` on badge (system resources)
|
||||
- `/Volumes/BADGER/main.py` → `/system/main.py` on badge (boot script)
|
||||
|
||||
**⚠️ Important**: Install the paginated menu to show unlimited apps (default shows only 6):
|
||||
- Download: https://raw.githubusercontent.com/badger/home/refs/heads/main/badge/apps/menu/__init__.py
|
||||
- Replace `/Volumes/BADGER/apps/menu/__init__.py` in Mass Storage Mode
|
||||
|
||||
## File Transfer Methods
|
||||
|
||||
### Using mpremote for Development (NOT for installing apps)
|
||||
|
||||
**⚠️ IMPORTANT**: You CANNOT use `mpremote` to install apps to `/system/apps/` because it's read-only. Use Mass Storage Mode for apps.
|
||||
|
||||
`mpremote` is useful for:
|
||||
- **Testing**: Running code temporarily without saving
|
||||
- **App Data**: Copying files to `/storage/` (writable partition)
|
||||
- **Development**: Quick iteration and debugging
|
||||
|
||||
```bash
|
||||
# Run app temporarily (doesn't save to badge)
|
||||
mpremote run my_app/__init__.py
|
||||
|
||||
# Copy app data to writable storage
|
||||
mpremote cp data.txt :/storage/data.txt
|
||||
|
||||
# Create directory in writable storage
|
||||
mpremote mkdir :/storage/mydata
|
||||
|
||||
# Copy from badge to computer
|
||||
mpremote cp :/storage/data.txt local_backup.txt
|
||||
|
||||
# List files on badge
|
||||
mpremote ls # Root directory
|
||||
mpremote ls /system/apps # MonaOS apps (read-only)
|
||||
mpremote ls /storage # Writable storage
|
||||
|
||||
# Remove file from storage (NOT /system/)
|
||||
mpremote rm :/storage/data.txt
|
||||
|
||||
# Remove directory from storage (NOT /system/)
|
||||
mpremote rm -rf :/storage/mydata
|
||||
```
|
||||
|
||||
**What you CANNOT do with mpremote**:
|
||||
- ❌ Install apps to `/system/apps/` (read-only)
|
||||
- ❌ Modify `/system/apps/menu/` (read-only)
|
||||
- ❌ Edit files in `/system/` (read-only)
|
||||
- ✅ Use USB Mass Storage Mode instead for these operations
|
||||
|
||||
### Using ampy
|
||||
|
||||
```bash
|
||||
# Install ampy
|
||||
pip install adafruit-ampy
|
||||
|
||||
# Set port (or use --port flag)
|
||||
export AMPY_PORT=/dev/tty.usbmodem*
|
||||
|
||||
# Upload file
|
||||
ampy put main.py
|
||||
ampy put config.py /lib/config.py
|
||||
|
||||
# Upload directory
|
||||
ampy put lib/
|
||||
|
||||
# Download file
|
||||
ampy get main.py
|
||||
|
||||
# List files
|
||||
ampy ls
|
||||
ampy ls /lib
|
||||
|
||||
# Remove file
|
||||
ampy rm old_file.py
|
||||
```
|
||||
|
||||
### Using rshell
|
||||
|
||||
```bash
|
||||
# Install rshell
|
||||
pip install rshell
|
||||
|
||||
# Connect
|
||||
rshell --port /dev/tty.usbmodem*
|
||||
|
||||
# Commands in rshell
|
||||
/your/computer> boards # List connected boards
|
||||
/your/computer> connect serial /dev/tty.usbmodem*
|
||||
/your/computer> cp main.py /pyboard/
|
||||
/your/computer> cp -r lib /pyboard/
|
||||
/your/computer> ls /pyboard
|
||||
/your/computer> cat /pyboard/main.py
|
||||
/your/computer> rm /pyboard/old.py
|
||||
```
|
||||
|
||||
### Manual File Sync via REPL
|
||||
|
||||
```python
|
||||
# In REPL - create/edit file directly
|
||||
f = open('config.py', 'w')
|
||||
f.write('''
|
||||
CONFIG = {
|
||||
'wifi_ssid': 'MyNetwork',
|
||||
'wifi_password': 'password123',
|
||||
'version': '1.0.0'
|
||||
}
|
||||
''')
|
||||
f.close()
|
||||
```
|
||||
|
||||
## Project Organization
|
||||
|
||||
### MonaOS App Structure
|
||||
|
||||
Each MonaOS app is a **directory** with this structure:
|
||||
|
||||
```
|
||||
my_app/ # Your app directory
|
||||
├── __init__.py # Entry point with update() function (required)
|
||||
├── icon.png # 24x24 PNG icon for launcher (required)
|
||||
├── assets/ # Optional: app resources (auto-added to path)
|
||||
│ ├── sprites.png
|
||||
│ ├── font.ppf
|
||||
│ └── config.json
|
||||
└── README.md # Optional: app documentation
|
||||
```
|
||||
|
||||
### Local Development Structure
|
||||
|
||||
Your development directory on your computer:
|
||||
|
||||
```
|
||||
badge-project/
|
||||
├── my_app/ # MonaOS app directory
|
||||
│ ├── __init__.py # App entry point
|
||||
│ ├── icon.png # 24x24 icon
|
||||
│ └── assets/ # App assets
|
||||
│ └── sprites.png
|
||||
├── another_app/ # Another MonaOS app
|
||||
│ ├── __init__.py
|
||||
│ └── icon.png
|
||||
├── requirements.txt # Python dependencies for development
|
||||
├── venv/ # Virtual environment
|
||||
└── deploy.sh # Deployment script
|
||||
```
|
||||
|
||||
### Deploy Script for MonaOS Apps
|
||||
|
||||
**⚠️ IMPORTANT**: Since `/system/apps/` is read-only via mpremote, this script uses USB Mass Storage Mode.
|
||||
|
||||
Create `deploy.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# deploy.sh - Deploy MonaOS app to badge via USB Mass Storage Mode
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: ./deploy.sh <app_name>"
|
||||
echo "Example: ./deploy.sh my_app"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APP_NAME=$1
|
||||
BADGE_MOUNT="/Volumes/BADGER"
|
||||
|
||||
if [ ! -d "$APP_NAME" ]; then
|
||||
echo "Error: App directory '$APP_NAME' not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if badge is in Mass Storage Mode
|
||||
if [ ! -d "$BADGE_MOUNT" ]; then
|
||||
echo "⚠️ Badge not found in Mass Storage Mode"
|
||||
echo ""
|
||||
echo "Please enter Mass Storage Mode:"
|
||||
echo " 1. Connect badge via USB-C"
|
||||
echo " 2. Press RESET button TWICE quickly"
|
||||
echo " 3. Wait for BADGER drive to appear"
|
||||
echo " 4. Run this script again"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Deploying $APP_NAME to MonaOS..."
|
||||
|
||||
# Verify required files exist
|
||||
if [ ! -f "$APP_NAME/__init__.py" ]; then
|
||||
echo "Error: __init__.py not found in $APP_NAME/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$APP_NAME/icon.png" ]; then
|
||||
echo "Warning: icon.png not found (required for launcher display)"
|
||||
fi
|
||||
|
||||
# Remove old version if it exists
|
||||
if [ -d "$BADGE_MOUNT/apps/$APP_NAME" ]; then
|
||||
echo "Removing old version..."
|
||||
rm -rf "$BADGE_MOUNT/apps/$APP_NAME"
|
||||
fi
|
||||
|
||||
# Copy app to badge
|
||||
echo "Copying app to badge..."
|
||||
cp -r "$APP_NAME" "$BADGE_MOUNT/apps/"
|
||||
|
||||
echo "✓ Deployment complete!"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Eject BADGER drive: diskutil eject /Volumes/BADGER"
|
||||
echo " 2. Press RESET once on badge to reboot"
|
||||
echo " 3. Your app will appear in MonaOS launcher"
|
||||
echo ""
|
||||
echo "Note: Install paginated menu for unlimited apps:"
|
||||
echo "https://raw.githubusercontent.com/badger/home/refs/heads/main/badge/apps/menu/__init__.py"
|
||||
```
|
||||
|
||||
Make executable: `chmod +x deploy.sh`
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
./deploy.sh my_app
|
||||
```
|
||||
|
||||
## Deployment Workflows
|
||||
|
||||
### Development Workflow
|
||||
|
||||
Quick iteration during development:
|
||||
|
||||
```bash
|
||||
# 1. Edit code locally
|
||||
vim my_app/__init__.py
|
||||
|
||||
# 2. Test app temporarily (doesn't save to badge)
|
||||
mpremote run my_app/__init__.py
|
||||
|
||||
# 3. Deploy to MonaOS launcher (use Mass Storage Mode)
|
||||
# - Press RESET twice on badge
|
||||
# - Copy updated files: cp -r my_app /Volumes/BADGER/apps/
|
||||
# - Eject and press RESET once
|
||||
|
||||
# 4. Verify deployment
|
||||
mpremote ls /system/apps/my_app
|
||||
|
||||
# 5. Launch from badge
|
||||
# Use physical buttons to navigate MonaOS menu and select your app
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
|
||||
Full deployment with verification:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# production-deploy.sh
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
BADGE_PORT="/dev/tty.usbmodem*"
|
||||
|
||||
echo "Starting production deployment..."
|
||||
|
||||
# Backup existing files
|
||||
echo "Creating backup..."
|
||||
mpremote connect $BADGE_PORT cp :main.py :main.py.backup
|
||||
mpremote connect $BADGE_PORT cp :config.py :config.py.backup
|
||||
|
||||
# Deploy new files
|
||||
echo "Deploying new version..."
|
||||
mpremote connect $BADGE_PORT cp main.py :main.py
|
||||
mpremote connect $BADGE_PORT cp config.py :config.py
|
||||
mpremote connect $BADGE_PORT cp -r lib/ :/lib/
|
||||
|
||||
# Verify deployment
|
||||
echo "Verifying deployment..."
|
||||
mpremote connect $BADGE_PORT exec "
|
||||
import sys
|
||||
print('MicroPython version:', sys.version)
|
||||
|
||||
# Import and verify main module
|
||||
try:
|
||||
import main
|
||||
print('✓ main.py imports successfully')
|
||||
except Exception as e:
|
||||
print('✗ Error importing main.py:', e)
|
||||
sys.exit(1)
|
||||
"
|
||||
|
||||
# Restart with new code
|
||||
echo "Restarting badge..."
|
||||
mpremote connect $BADGE_PORT exec "import machine; machine.soft_reset()"
|
||||
|
||||
echo "Production deployment complete!"
|
||||
```
|
||||
|
||||
### Staged Deployment
|
||||
|
||||
Test on one badge before deploying to multiple:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# staged-deploy.sh
|
||||
|
||||
# Stage 1: Deploy to test badge
|
||||
echo "Stage 1: Deploying to test badge..."
|
||||
./deploy.sh --port /dev/tty.usbmodem1
|
||||
|
||||
# Stage 2: Run automated tests
|
||||
echo "Stage 2: Running tests..."
|
||||
python test_suite.py --badge /dev/tty.usbmodem1
|
||||
|
||||
# Stage 3: Deploy to production badges
|
||||
echo "Stage 3: Deploying to all badges..."
|
||||
for port in /dev/tty.usbmodem*; do
|
||||
echo "Deploying to $port..."
|
||||
./deploy.sh --port $port
|
||||
done
|
||||
|
||||
echo "Staged deployment complete!"
|
||||
```
|
||||
|
||||
## File Management
|
||||
|
||||
### Sync Local and Badge Files
|
||||
|
||||
```python
|
||||
# sync.py - Sync files between computer and badge
|
||||
import os
|
||||
import subprocess
|
||||
import hashlib
|
||||
|
||||
def get_local_files(path='.'):
|
||||
"""Get list of local .py files"""
|
||||
files = []
|
||||
for root, dirs, filenames in os.walk(path):
|
||||
# Skip test directories
|
||||
if 'test' in root:
|
||||
continue
|
||||
for filename in filenames:
|
||||
if filename.endswith('.py'):
|
||||
filepath = os.path.join(root, filename)
|
||||
files.append(filepath)
|
||||
return files
|
||||
|
||||
def sync_to_badge(port='/dev/tty.usbmodem*'):
|
||||
"""Sync all Python files to badge"""
|
||||
files = get_local_files()
|
||||
|
||||
print(f"Syncing {len(files)} files to badge...")
|
||||
for filepath in files:
|
||||
# Determine badge path
|
||||
if filepath.startswith('./lib/'):
|
||||
badge_path = f":/{filepath[2:]}"
|
||||
elif filepath.startswith('./'):
|
||||
badge_path = f":/{filepath[2:]}"
|
||||
else:
|
||||
badge_path = f":/{filepath}"
|
||||
|
||||
# Upload file
|
||||
cmd = f"mpremote connect {port} cp {filepath} {badge_path}"
|
||||
print(f" {filepath} -> {badge_path}")
|
||||
subprocess.run(cmd, shell=True, check=True)
|
||||
|
||||
print("Sync complete!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
sync_to_badge()
|
||||
```
|
||||
|
||||
### Clean Up Badge Files
|
||||
|
||||
```python
|
||||
# cleanup.py - Remove unused files from badge
|
||||
|
||||
def cleanup_badge():
|
||||
"""Remove temporary and cache files from badge"""
|
||||
import os
|
||||
|
||||
# Files to remove
|
||||
patterns = [
|
||||
'.pyc', # Compiled Python
|
||||
'.backup', # Backup files
|
||||
'.tmp', # Temporary files
|
||||
'debug_', # Debug files
|
||||
]
|
||||
|
||||
removed = []
|
||||
|
||||
for path in ['/', '/lib']:
|
||||
try:
|
||||
files = os.listdir(path)
|
||||
for file in files:
|
||||
# Check if file matches cleanup pattern
|
||||
if any(file.endswith(p) or file.startswith(p) for p in patterns):
|
||||
filepath = f"{path}/{file}".replace('//', '/')
|
||||
os.remove(filepath)
|
||||
removed.append(filepath)
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f"Cleanup complete! Removed {len(removed)} files:")
|
||||
for f in removed:
|
||||
print(f" {f}")
|
||||
|
||||
# Run on badge via mpremote:
|
||||
# mpremote exec "$(cat cleanup.py)"
|
||||
```
|
||||
|
||||
### List Badge Files
|
||||
|
||||
```bash
|
||||
# list_files.sh - Comprehensive file listing
|
||||
|
||||
echo "Files on badge:"
|
||||
echo "==============="
|
||||
|
||||
echo -e "\nRoot directory:"
|
||||
mpremote ls
|
||||
|
||||
echo -e "\n/lib directory:"
|
||||
mpremote ls /lib 2>/dev/null || echo " (not found)"
|
||||
|
||||
echo -e "\n/assets directory:"
|
||||
mpremote ls /assets 2>/dev/null || echo " (not found)"
|
||||
|
||||
echo -e "\n/data directory:"
|
||||
mpremote ls /data 2>/dev/null || echo " (not found)"
|
||||
```
|
||||
|
||||
## Backup and Restore
|
||||
|
||||
### Backup Badge to Computer
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# backup.sh - Backup all files from badge
|
||||
|
||||
BACKUP_DIR="backup_$(date +%Y%m%d_%H%M%S)"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
echo "Backing up badge to $BACKUP_DIR..."
|
||||
|
||||
# Backup root Python files
|
||||
for file in $(mpremote ls | grep '.py'); do
|
||||
echo " Backing up $file..."
|
||||
mpremote cp ":$file" "$BACKUP_DIR/$file"
|
||||
done
|
||||
|
||||
# Backup lib directory
|
||||
mkdir -p "$BACKUP_DIR/lib"
|
||||
for file in $(mpremote ls /lib 2>/dev/null | grep '.py'); do
|
||||
echo " Backing up /lib/$file..."
|
||||
mpremote cp ":/lib/$file" "$BACKUP_DIR/lib/$file"
|
||||
done
|
||||
|
||||
echo "Backup complete: $BACKUP_DIR"
|
||||
```
|
||||
|
||||
### Restore Badge from Backup
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# restore.sh - Restore files to badge from backup
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: ./restore.sh <backup_directory>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BACKUP_DIR=$1
|
||||
|
||||
echo "Restoring from $BACKUP_DIR..."
|
||||
|
||||
# Restore root files
|
||||
for file in "$BACKUP_DIR"/*.py; do
|
||||
if [ -f "$file" ]; then
|
||||
filename=$(basename "$file")
|
||||
echo " Restoring $filename..."
|
||||
mpremote cp "$file" ":$filename"
|
||||
fi
|
||||
done
|
||||
|
||||
# Restore lib directory
|
||||
if [ -d "$BACKUP_DIR/lib" ]; then
|
||||
mpremote mkdir /lib 2>/dev/null || true
|
||||
for file in "$BACKUP_DIR/lib"/*.py; do
|
||||
if [ -f "$file" ]; then
|
||||
filename=$(basename "$file")
|
||||
echo " Restoring /lib/$filename..."
|
||||
mpremote cp "$file" ":/lib/$filename"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Restore complete!"
|
||||
```
|
||||
|
||||
## Version Management
|
||||
|
||||
### Version File
|
||||
|
||||
Create `version.py` on badge:
|
||||
|
||||
```python
|
||||
# version.py
|
||||
VERSION = '1.2.3'
|
||||
BUILD_DATE = '2024-01-15'
|
||||
COMMIT = 'abc123f'
|
||||
|
||||
def show():
|
||||
print(f"Version: {VERSION}")
|
||||
print(f"Build: {BUILD_DATE}")
|
||||
print(f"Commit: {COMMIT}")
|
||||
```
|
||||
|
||||
### Auto-generate Version
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# generate_version.sh - Auto-generate version file
|
||||
|
||||
VERSION=$(git describe --tags --always)
|
||||
BUILD_DATE=$(date +%Y-%m-%d)
|
||||
COMMIT=$(git rev-parse --short HEAD)
|
||||
|
||||
cat > version.py <<EOF
|
||||
# Auto-generated version file
|
||||
VERSION = '$VERSION'
|
||||
BUILD_DATE = '$BUILD_DATE'
|
||||
COMMIT = '$COMMIT'
|
||||
EOF
|
||||
|
||||
echo "Generated version.py: $VERSION ($BUILD_DATE)"
|
||||
```
|
||||
|
||||
Add to deploy script:
|
||||
|
||||
```bash
|
||||
# Generate version before deploy
|
||||
./generate_version.sh
|
||||
mpremote cp version.py :version.py
|
||||
```
|
||||
|
||||
## Multi-Badge Deployment
|
||||
|
||||
### Deploy to Multiple Badges
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# multi-deploy.sh - Deploy to multiple connected badges
|
||||
|
||||
echo "Scanning for connected badges..."
|
||||
BADGES=$(ls /dev/tty.usbmodem* 2>/dev/null)
|
||||
|
||||
if [ -z "$BADGES" ]; then
|
||||
echo "No badges found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found badges:"
|
||||
echo "$BADGES"
|
||||
echo
|
||||
|
||||
read -p "Deploy to all? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for PORT in $BADGES; do
|
||||
echo "================================"
|
||||
echo "Deploying to $PORT..."
|
||||
echo "================================"
|
||||
|
||||
mpremote connect $PORT cp main.py :main.py
|
||||
mpremote connect $PORT cp config.py :config.py
|
||||
mpremote connect $PORT cp -r lib/ :/lib/
|
||||
|
||||
echo "✓ Deployed to $PORT"
|
||||
echo
|
||||
done
|
||||
|
||||
echo "All badges updated!"
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Example
|
||||
|
||||
```yaml
|
||||
# .github/workflows/deploy.yml
|
||||
name: Deploy to Badge
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install mpremote pytest
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
pytest tests/
|
||||
|
||||
deploy:
|
||||
needs: test
|
||||
runs-on: self-hosted # Runner with badge connected
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Deploy to badge
|
||||
run: |
|
||||
./generate_version.sh
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
### Pre-commit Hook
|
||||
|
||||
```bash
|
||||
# .git/hooks/pre-commit
|
||||
#!/bin/bash
|
||||
# Run tests before allowing commit
|
||||
|
||||
echo "Running tests..."
|
||||
python -m pytest tests/
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Tests failed! Commit aborted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Tests passed!"
|
||||
exit 0
|
||||
```
|
||||
|
||||
Make executable: `chmod +x .git/hooks/pre-commit`
|
||||
|
||||
## Hot Reload Development
|
||||
|
||||
### Watch and Auto-deploy
|
||||
|
||||
```python
|
||||
# watch.py - Watch for file changes and auto-deploy
|
||||
import time
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
def get_file_hash(filepath):
|
||||
"""Get hash of file contents"""
|
||||
import hashlib
|
||||
with open(filepath, 'rb') as f:
|
||||
return hashlib.md5(f.read()).hexdigest()
|
||||
|
||||
def watch_and_deploy(watch_files, port='/dev/tty.usbmodem*'):
|
||||
"""Watch files and deploy on change"""
|
||||
print(f"Watching {len(watch_files)} files for changes...")
|
||||
|
||||
file_hashes = {f: get_file_hash(f) for f in watch_files}
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1) # Check every second
|
||||
|
||||
for filepath in watch_files:
|
||||
current_hash = get_file_hash(filepath)
|
||||
|
||||
if current_hash != file_hashes[filepath]:
|
||||
print(f"\n{filepath} changed! Deploying...")
|
||||
|
||||
# Deploy changed file
|
||||
badge_path = f":/{filepath}"
|
||||
cmd = f"mpremote connect {port} cp {filepath} {badge_path}"
|
||||
subprocess.run(cmd, shell=True)
|
||||
|
||||
# Soft reset badge
|
||||
cmd = f"mpremote connect {port} exec 'import machine; machine.soft_reset()'"
|
||||
subprocess.run(cmd, shell=True)
|
||||
|
||||
# Update hash
|
||||
file_hashes[filepath] = current_hash
|
||||
|
||||
print("Deploy complete!")
|
||||
except KeyboardInterrupt:
|
||||
print("\nStopped watching.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
watch_files = ['main.py', 'config.py', 'lib/display.py']
|
||||
watch_and_deploy(watch_files)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Pre-deployment Checklist
|
||||
|
||||
- [ ] Test code locally (if possible)
|
||||
- [ ] Update version number
|
||||
- [ ] Run linter/formatter
|
||||
- [ ] Commit changes to git
|
||||
- [ ] Backup current badge code
|
||||
- [ ] Deploy to test badge first
|
||||
- [ ] Verify deployment success
|
||||
- [ ] Test on badge
|
||||
- [ ] Deploy to production badges
|
||||
|
||||
### File Organization Tips
|
||||
|
||||
1. **Keep main.py simple** - Import from modules
|
||||
2. **Use /lib for reusable code** - Separate concerns
|
||||
3. **Store data in /data** - Keep code and data separate
|
||||
4. **Version control everything** - Except secrets
|
||||
5. **Use .gitignore** - Don't commit badge-specific configs
|
||||
|
||||
### Deployment Optimization
|
||||
|
||||
```bash
|
||||
# Minimize deployment time
|
||||
|
||||
# Deploy only changed files
|
||||
git diff --name-only HEAD~1 | grep '.py$' | while read file; do
|
||||
mpremote cp "$file" ":/$file"
|
||||
done
|
||||
|
||||
# Compress files before transfer (if network-based)
|
||||
# gzip files, transfer, decompress on badge
|
||||
|
||||
# Parallel deployment to multiple badges
|
||||
parallel -j 4 ./deploy.sh --port ::: /dev/tty.usbmodem*
|
||||
```
|
||||
|
||||
## Troubleshooting Deployment
|
||||
|
||||
**Files not updating**: Verify file was actually copied, check paths with `mpremote ls /system/apps/my_app`
|
||||
|
||||
**Permission denied**: Badge may be in use by another process, close other connections
|
||||
|
||||
**Deployment fails silently**: Check disk space on badge, verify file syntax, ensure `__init__.py` and `icon.png` exist
|
||||
|
||||
**App doesn't appear in MonaOS menu**:
|
||||
- Verify files uploaded to `/system/apps/my_app/`
|
||||
- Check icon is exactly 24x24 PNG
|
||||
- Default menu shows only 6 apps - enable pagination: https://badger.github.io/hack/menu-pagination/
|
||||
|
||||
**Changes not reflected**: Restart badge to reload app code
|
||||
|
||||
**Slow transfers**: USB cable quality matters, try different cable/port
|
||||
|
||||
## Official Examples
|
||||
|
||||
For inspiration on what to deploy to your badge:
|
||||
- **Hacks**: https://badger.github.io/hacks/ - Step-by-step customization tutorials
|
||||
- **Apps**: https://badger.github.io/apps/ - Loadable MonaOS apps (Commits, Snake)
|
||||
- **Source Code**: https://github.com/badger/home/tree/main/badgerware - Browse official MonaOS app code and API docs
|
||||
|
||||
With these deployment workflows, you can efficiently manage and deploy applications to your Universe 2025 Badge's MonaOS launcher!
|
||||
766
skills/badger-diagnostics/SKILL.md
Normal file
766
skills/badger-diagnostics/SKILL.md
Normal file
@@ -0,0 +1,766 @@
|
||||
---
|
||||
name: badger-diagnostics
|
||||
description: System diagnostics, verification, and troubleshooting for Badger 2350. Use when checking firmware version, verifying installations, diagnosing hardware issues, troubleshooting errors, or performing system health checks on Badger 2350.
|
||||
---
|
||||
|
||||
# Badger 2350 Diagnostics and Troubleshooting
|
||||
|
||||
Comprehensive diagnostics and troubleshooting tools for verifying your Badger 2350 setup, checking installations, and resolving common issues.
|
||||
|
||||
## ⚠️ When to Use This Skill
|
||||
|
||||
**Use this skill FIRST** in these situations:
|
||||
1. **Starting a new session** - Verify everything works before coding
|
||||
2. **After setup** - Confirm installation completed correctly
|
||||
3. **Before debugging** - Rule out environment issues
|
||||
4. **When errors occur** - Diagnose the root cause
|
||||
5. **After firmware updates** - Verify everything still works
|
||||
|
||||
**Best Practice**: Run diagnostics at the start of EVERY development session. It takes 30 seconds and prevents hours of debugging.
|
||||
|
||||
## Quick Verification Command
|
||||
|
||||
**Run this FIRST every session** (doesn't require any files to exist):
|
||||
|
||||
### Level 1: Basic Connection Test
|
||||
```bash
|
||||
# Simplest test - just verify badge responds
|
||||
mpremote exec "print('Badge connected!')"
|
||||
# Should print: Badge connected!
|
||||
|
||||
# If this fails, badge isn't connected or mpremote not installed
|
||||
```
|
||||
|
||||
### Level 2: Full Verification
|
||||
```bash
|
||||
# Complete verification (auto-detects port on macOS/Linux)
|
||||
mpremote exec "import sys, gc; from badgeware import screen, brushes, shapes, io; print('=== VERIFICATION ==='); print('✓ MicroPython:', sys.version[:30]); print('✓ Memory:', gc.mem_free(), 'bytes'); print('✓ badgeware: loaded'); print('✓ Display: 160x120'); print('=== ALL OK ===')"
|
||||
```
|
||||
|
||||
**Expected output**: All checks with ✓ marks and no errors.
|
||||
|
||||
**With explicit port** (if auto-detect fails):
|
||||
```bash
|
||||
mpremote connect /dev/cu.usbmodem1101 exec "from badgeware import screen; print('✓ Badge OK')"
|
||||
# Replace /dev/cu.usbmodem1101 with your port
|
||||
```
|
||||
|
||||
**If this fails**: Continue with detailed diagnostics below.
|
||||
|
||||
## Quick System Check
|
||||
|
||||
Run this complete system diagnostic in REPL:
|
||||
|
||||
```python
|
||||
# diagnostic.py - Complete system check
|
||||
import sys
|
||||
import gc
|
||||
import os
|
||||
from machine import freq, unique_id
|
||||
import ubinascii
|
||||
|
||||
def system_info():
|
||||
"""Display complete system information"""
|
||||
print("=" * 50)
|
||||
print("BADGER 2350 SYSTEM DIAGNOSTICS")
|
||||
print("=" * 50)
|
||||
|
||||
# MicroPython version
|
||||
print(f"\n[MicroPython]")
|
||||
print(f" Version: {sys.version}")
|
||||
print(f" Implementation: {sys.implementation}")
|
||||
print(f" Platform: {sys.platform}")
|
||||
|
||||
# Hardware info
|
||||
print(f"\n[Hardware]")
|
||||
print(f" CPU Frequency: {freq():,} Hz ({freq() / 1_000_000:.0f} MHz)")
|
||||
uid = ubinascii.hexlify(unique_id()).decode()
|
||||
print(f" Unique ID: {uid}")
|
||||
|
||||
# Memory
|
||||
gc.collect()
|
||||
print(f"\n[Memory]")
|
||||
print(f" Free: {gc.mem_free():,} bytes ({gc.mem_free() / 1024:.1f} KB)")
|
||||
print(f" Allocated: {gc.mem_alloc():,} bytes ({gc.mem_alloc() / 1024:.1f} KB)")
|
||||
total = gc.mem_free() + gc.mem_alloc()
|
||||
print(f" Total: {total:,} bytes ({total / 1024:.1f} KB)")
|
||||
|
||||
# File system
|
||||
print(f"\n[File System]")
|
||||
try:
|
||||
stat = os.statvfs('/')
|
||||
block_size = stat[0]
|
||||
total_blocks = stat[2]
|
||||
free_blocks = stat[3]
|
||||
total_bytes = block_size * total_blocks
|
||||
free_bytes = block_size * free_blocks
|
||||
used_bytes = total_bytes - free_bytes
|
||||
|
||||
print(f" Total: {total_bytes:,} bytes ({total_bytes / 1024 / 1024:.2f} MB)")
|
||||
print(f" Used: {used_bytes:,} bytes ({used_bytes / 1024 / 1024:.2f} MB)")
|
||||
print(f" Free: {free_bytes:,} bytes ({free_bytes / 1024 / 1024:.2f} MB)")
|
||||
except:
|
||||
print(" Unable to check filesystem")
|
||||
|
||||
# Module path
|
||||
print(f"\n[Module Search Paths]")
|
||||
for path in sys.path:
|
||||
print(f" {path}")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
|
||||
# Run diagnostic
|
||||
system_info()
|
||||
```
|
||||
|
||||
## Firmware Version Check
|
||||
|
||||
### Check MicroPython Firmware
|
||||
|
||||
```python
|
||||
import sys
|
||||
|
||||
# Full version info
|
||||
print(sys.version)
|
||||
# Example: 3.4.0; MicroPython v1.20.0 on 2023-04-26
|
||||
|
||||
# Implementation details
|
||||
print(sys.implementation)
|
||||
# (name='micropython', version=(1, 20, 0), _machine='Raspberry Pi Pico W with RP2040', _mpy=6182)
|
||||
|
||||
# Extract version number
|
||||
version = sys.implementation.version
|
||||
print(f"MicroPython {version[0]}.{version[1]}.{version[2]}")
|
||||
```
|
||||
|
||||
### Check Badger Library Version
|
||||
|
||||
```python
|
||||
import badger2040
|
||||
|
||||
# Check if version attribute exists
|
||||
if hasattr(badger2040, '__version__'):
|
||||
print(f"Badger library version: {badger2040.__version__}")
|
||||
else:
|
||||
print("Badger library version not available")
|
||||
|
||||
# Check file location
|
||||
print(f"Badger library: {badger2040.__file__}")
|
||||
```
|
||||
|
||||
### Recommended Firmware Versions
|
||||
|
||||
Verify you have compatible firmware:
|
||||
|
||||
```python
|
||||
def check_firmware_compatibility():
|
||||
"""Check if firmware is compatible with Badger 2350"""
|
||||
version = sys.implementation.version
|
||||
|
||||
if version[0] >= 1 and version[1] >= 20:
|
||||
print("✓ MicroPython version is compatible")
|
||||
return True
|
||||
else:
|
||||
print("✗ MicroPython version may be outdated")
|
||||
print(" Recommended: MicroPython 1.20+")
|
||||
print(f" Current: {version[0]}.{version[1]}.{version[2]}")
|
||||
return False
|
||||
|
||||
check_firmware_compatibility()
|
||||
```
|
||||
|
||||
## Module Verification
|
||||
|
||||
### Check Core Modules
|
||||
|
||||
```python
|
||||
def verify_core_modules():
|
||||
"""Verify essential modules are available"""
|
||||
required_modules = {
|
||||
'badger2040': 'Badger display library',
|
||||
'machine': 'Hardware interface',
|
||||
'time': 'Time functions',
|
||||
'gc': 'Garbage collection',
|
||||
'sys': 'System functions',
|
||||
'os': 'Operating system interface'
|
||||
}
|
||||
|
||||
optional_modules = {
|
||||
'network': 'WiFi support',
|
||||
'urequests': 'HTTP client',
|
||||
'ujson': 'JSON parsing',
|
||||
'ubinascii': 'Binary/ASCII conversion'
|
||||
}
|
||||
|
||||
print("Checking required modules...")
|
||||
all_ok = True
|
||||
for module, description in required_modules.items():
|
||||
try:
|
||||
__import__(module)
|
||||
print(f" ✓ {module:15s} - {description}")
|
||||
except ImportError:
|
||||
print(f" ✗ {module:15s} - MISSING - {description}")
|
||||
all_ok = False
|
||||
|
||||
print("\nChecking optional modules...")
|
||||
for module, description in optional_modules.items():
|
||||
try:
|
||||
__import__(module)
|
||||
print(f" ✓ {module:15s} - {description}")
|
||||
except ImportError:
|
||||
print(f" ○ {module:15s} - Not installed - {description}")
|
||||
|
||||
return all_ok
|
||||
|
||||
verify_core_modules()
|
||||
```
|
||||
|
||||
### List All Installed Packages
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
def list_installed_packages():
|
||||
"""List all installed packages in /lib"""
|
||||
print("Installed packages:")
|
||||
|
||||
# Check /lib directory
|
||||
try:
|
||||
lib_contents = os.listdir('/lib')
|
||||
if lib_contents:
|
||||
for item in sorted(lib_contents):
|
||||
# Try to get more info
|
||||
path = f'/lib/{item}'
|
||||
try:
|
||||
stat = os.stat(path)
|
||||
size = stat[6] # File size
|
||||
print(f" {item:30s} {size:8,} bytes")
|
||||
except:
|
||||
print(f" {item}")
|
||||
else:
|
||||
print(" (no packages in /lib)")
|
||||
except OSError:
|
||||
print(" /lib directory not found")
|
||||
|
||||
# Check root directory for .py files
|
||||
print("\nRoot directory modules:")
|
||||
root_contents = os.listdir('/')
|
||||
py_files = [f for f in root_contents if f.endswith('.py')]
|
||||
for f in sorted(py_files):
|
||||
stat = os.stat(f)
|
||||
size = stat[6]
|
||||
print(f" {f:30s} {size:8,} bytes")
|
||||
|
||||
list_installed_packages()
|
||||
```
|
||||
|
||||
## Hardware Diagnostics
|
||||
|
||||
### Display Test
|
||||
|
||||
```python
|
||||
import badger2040
|
||||
|
||||
def test_display():
|
||||
"""Test display functionality"""
|
||||
print("Testing display...")
|
||||
|
||||
badge = badger2040.Badger2040()
|
||||
|
||||
# Test 1: Clear screen
|
||||
badge.set_pen(15)
|
||||
badge.clear()
|
||||
badge.update()
|
||||
print(" ✓ Clear screen")
|
||||
|
||||
# Test 2: Draw text
|
||||
badge.set_pen(0)
|
||||
badge.text("Display Test", 10, 10, scale=2)
|
||||
badge.update()
|
||||
print(" ✓ Draw text")
|
||||
|
||||
# Test 3: Draw shapes
|
||||
badge.line(10, 40, 100, 40)
|
||||
badge.rectangle(10, 50, 50, 30)
|
||||
badge.update()
|
||||
print(" ✓ Draw shapes")
|
||||
|
||||
print("Display test complete!")
|
||||
|
||||
test_display()
|
||||
```
|
||||
|
||||
### Button Test
|
||||
|
||||
```python
|
||||
import badger2040
|
||||
import time
|
||||
|
||||
def test_buttons():
|
||||
"""Test all buttons"""
|
||||
print("Button test - Press each button:")
|
||||
print(" A, B, C, UP, DOWN")
|
||||
print("Press Ctrl+C to exit")
|
||||
|
||||
badge = badger2040.Badger2040()
|
||||
tested = set()
|
||||
|
||||
while len(tested) < 5:
|
||||
if badge.pressed(badger2040.BUTTON_A) and 'A' not in tested:
|
||||
print(" ✓ Button A works")
|
||||
tested.add('A')
|
||||
elif badge.pressed(badger2040.BUTTON_B) and 'B' not in tested:
|
||||
print(" ✓ Button B works")
|
||||
tested.add('B')
|
||||
elif badge.pressed(badger2040.BUTTON_C) and 'C' not in tested:
|
||||
print(" ✓ Button C works")
|
||||
tested.add('C')
|
||||
elif badge.pressed(badger2040.BUTTON_UP) and 'UP' not in tested:
|
||||
print(" ✓ Button UP works")
|
||||
tested.add('UP')
|
||||
elif badge.pressed(badger2040.BUTTON_DOWN) and 'DOWN' not in tested:
|
||||
print(" ✓ Button DOWN works")
|
||||
tested.add('DOWN')
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
print("All buttons tested successfully!")
|
||||
|
||||
test_buttons()
|
||||
```
|
||||
|
||||
### GPIO Test
|
||||
|
||||
```python
|
||||
from machine import Pin
|
||||
|
||||
def test_gpio():
|
||||
"""Test GPIO pins"""
|
||||
print("Testing GPIO pins...")
|
||||
|
||||
# Test output
|
||||
test_pin = Pin(25, Pin.OUT)
|
||||
test_pin.value(1)
|
||||
print(f" ✓ Pin 25 set to HIGH: {test_pin.value()}")
|
||||
test_pin.value(0)
|
||||
print(f" ✓ Pin 25 set to LOW: {test_pin.value()}")
|
||||
|
||||
# Test input with pull-up
|
||||
input_pin = Pin(15, Pin.IN, Pin.PULL_UP)
|
||||
print(f" ✓ Pin 15 input (pull-up): {input_pin.value()}")
|
||||
|
||||
print("GPIO test complete!")
|
||||
|
||||
test_gpio()
|
||||
```
|
||||
|
||||
### I2C Bus Scan
|
||||
|
||||
```python
|
||||
from machine import I2C, Pin
|
||||
|
||||
def scan_i2c():
|
||||
"""Scan for I2C devices"""
|
||||
print("Scanning I2C bus...")
|
||||
|
||||
i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
|
||||
devices = i2c.scan()
|
||||
|
||||
if devices:
|
||||
print(f" Found {len(devices)} device(s):")
|
||||
for device in devices:
|
||||
print(f" 0x{device:02X} ({device})")
|
||||
else:
|
||||
print(" No I2C devices found")
|
||||
|
||||
return devices
|
||||
|
||||
scan_i2c()
|
||||
```
|
||||
|
||||
## Network Diagnostics
|
||||
|
||||
### WiFi Connection Test
|
||||
|
||||
```python
|
||||
import network
|
||||
import time
|
||||
|
||||
def test_wifi(ssid, password, timeout=10):
|
||||
"""Test WiFi connection"""
|
||||
print(f"Testing WiFi connection to '{ssid}'...")
|
||||
|
||||
wlan = network.WLAN(network.STA_IF)
|
||||
wlan.active(True)
|
||||
|
||||
# Check if already connected
|
||||
if wlan.isconnected():
|
||||
print(" ✓ Already connected")
|
||||
print(f" IP: {wlan.ifconfig()[0]}")
|
||||
return True
|
||||
|
||||
# Attempt connection
|
||||
print(" Connecting...")
|
||||
wlan.connect(ssid, password)
|
||||
|
||||
# Wait for connection
|
||||
start = time.time()
|
||||
while not wlan.isconnected() and (time.time() - start) < timeout:
|
||||
time.sleep(0.5)
|
||||
print(".", end="")
|
||||
|
||||
print() # New line
|
||||
|
||||
if wlan.isconnected():
|
||||
config = wlan.ifconfig()
|
||||
print(" ✓ Connected successfully")
|
||||
print(f" IP Address: {config[0]}")
|
||||
print(f" Subnet Mask: {config[1]}")
|
||||
print(f" Gateway: {config[2]}")
|
||||
print(f" DNS: {config[3]}")
|
||||
print(f" Signal Strength: {wlan.status('rssi')} dBm")
|
||||
return True
|
||||
else:
|
||||
print(" ✗ Connection failed")
|
||||
status = wlan.status()
|
||||
print(f" Status code: {status}")
|
||||
return False
|
||||
|
||||
# Usage
|
||||
# test_wifi('YourSSID', 'YourPassword')
|
||||
```
|
||||
|
||||
### Network Speed Test
|
||||
|
||||
```python
|
||||
import urequests
|
||||
import time
|
||||
|
||||
def test_network_speed():
|
||||
"""Test network download speed"""
|
||||
print("Testing network speed...")
|
||||
|
||||
url = "http://httpbin.org/bytes/10000" # 10KB test file
|
||||
|
||||
try:
|
||||
start = time.ticks_ms()
|
||||
response = urequests.get(url)
|
||||
end = time.ticks_ms()
|
||||
|
||||
size = len(response.content)
|
||||
duration = time.ticks_diff(end, start) / 1000 # Convert to seconds
|
||||
|
||||
speed = (size / duration) / 1024 # KB/s
|
||||
|
||||
print(f" Downloaded: {size} bytes")
|
||||
print(f" Time: {duration:.2f}s")
|
||||
print(f" Speed: {speed:.2f} KB/s")
|
||||
|
||||
response.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f" ✗ Network test failed: {e}")
|
||||
return False
|
||||
|
||||
# test_network_speed()
|
||||
```
|
||||
|
||||
## Memory Diagnostics
|
||||
|
||||
### Memory Usage Analysis
|
||||
|
||||
```python
|
||||
import gc
|
||||
|
||||
def analyze_memory():
|
||||
"""Analyze memory usage"""
|
||||
print("Memory Analysis:")
|
||||
|
||||
# Before collection
|
||||
free_before = gc.mem_free()
|
||||
alloc_before = gc.mem_alloc()
|
||||
|
||||
# Collect garbage
|
||||
gc.collect()
|
||||
|
||||
# After collection
|
||||
free_after = gc.mem_free()
|
||||
alloc_after = gc.mem_alloc()
|
||||
|
||||
print(f"\nBefore garbage collection:")
|
||||
print(f" Free: {free_before:,} bytes ({free_before / 1024:.1f} KB)")
|
||||
print(f" Allocated: {alloc_before:,} bytes ({alloc_before / 1024:.1f} KB)")
|
||||
|
||||
print(f"\nAfter garbage collection:")
|
||||
print(f" Free: {free_after:,} bytes ({free_after / 1024:.1f} KB)")
|
||||
print(f" Allocated: {alloc_after:,} bytes ({alloc_after / 1024:.1f} KB)")
|
||||
|
||||
freed = free_after - free_before
|
||||
print(f"\nReclaimed: {freed:,} bytes ({freed / 1024:.1f} KB)")
|
||||
|
||||
# Total memory
|
||||
total = free_after + alloc_after
|
||||
usage_percent = (alloc_after / total) * 100
|
||||
|
||||
print(f"\nTotal memory: {total:,} bytes ({total / 1024:.1f} KB)")
|
||||
print(f"Usage: {usage_percent:.1f}%")
|
||||
|
||||
# Warning if low
|
||||
if free_after < 10000:
|
||||
print("\n⚠ WARNING: Low memory!")
|
||||
elif free_after < 50000:
|
||||
print("\n⚠ CAUTION: Memory running low")
|
||||
else:
|
||||
print("\n✓ Memory usage looks good")
|
||||
|
||||
analyze_memory()
|
||||
```
|
||||
|
||||
### Find Memory Leaks
|
||||
|
||||
```python
|
||||
import gc
|
||||
|
||||
def find_memory_leaks(function, iterations=10):
|
||||
"""Test function for memory leaks"""
|
||||
print(f"Testing for memory leaks ({iterations} iterations)...")
|
||||
|
||||
gc.collect()
|
||||
initial_mem = gc.mem_free()
|
||||
|
||||
for i in range(iterations):
|
||||
function()
|
||||
gc.collect()
|
||||
|
||||
current_mem = gc.mem_free()
|
||||
leaked = initial_mem - current_mem
|
||||
|
||||
if leaked > 0:
|
||||
print(f" Iteration {i+1}: Leaked {leaked} bytes")
|
||||
|
||||
gc.collect()
|
||||
final_mem = gc.mem_free()
|
||||
total_leaked = initial_mem - final_mem
|
||||
|
||||
if total_leaked > 100: # Allow small variance
|
||||
print(f"⚠ Possible memory leak: {total_leaked} bytes leaked")
|
||||
else:
|
||||
print(f"✓ No significant memory leak detected")
|
||||
|
||||
# Usage
|
||||
# def test_func():
|
||||
# data = [i for i in range(100)]
|
||||
# find_memory_leaks(test_func)
|
||||
```
|
||||
|
||||
## Error Diagnosis
|
||||
|
||||
### Common Error Patterns
|
||||
|
||||
```python
|
||||
def diagnose_error(error):
|
||||
"""Provide diagnosis for common errors"""
|
||||
error_str = str(error)
|
||||
|
||||
diagnostics = {
|
||||
'ImportError': """
|
||||
Module not found. Check:
|
||||
- Module is installed (use mip.install())
|
||||
- Module is in /lib or root directory
|
||||
- Module name is spelled correctly
|
||||
- File has .py extension
|
||||
""",
|
||||
|
||||
'MemoryError': """
|
||||
Out of memory. Try:
|
||||
- Run gc.collect() before allocation
|
||||
- Reduce variable scope
|
||||
- Use generators instead of lists
|
||||
- Break large operations into smaller chunks
|
||||
- Delete unused objects with 'del'
|
||||
""",
|
||||
|
||||
'OSError': """
|
||||
File/Hardware operation failed. Check:
|
||||
- File path is correct
|
||||
- File exists (for reading)
|
||||
- Filesystem not full (for writing)
|
||||
- Hardware is connected properly
|
||||
- Pins are not already in use
|
||||
""",
|
||||
|
||||
'AttributeError': """
|
||||
Attribute not found. Check:
|
||||
- Object has the attribute/method
|
||||
- Spelling is correct
|
||||
- Module is imported correctly
|
||||
- Object is initialized
|
||||
""",
|
||||
|
||||
'ValueError': """
|
||||
Invalid value. Check:
|
||||
- Parameter values are in valid range
|
||||
- Data types match expected types
|
||||
- String formats are correct
|
||||
"""
|
||||
}
|
||||
|
||||
# Find matching error type
|
||||
for error_type, advice in diagnostics.items():
|
||||
if error_type in error_str:
|
||||
print(f"Diagnosis for {error_type}:")
|
||||
print(advice)
|
||||
return
|
||||
|
||||
print("Error type not recognized. Common debugging steps:")
|
||||
print("- Check error message carefully")
|
||||
print("- Print variable values before error")
|
||||
print("- Simplify code to isolate problem")
|
||||
print("- Check MicroPython documentation")
|
||||
|
||||
# Usage
|
||||
# try:
|
||||
# import nonexistent_module
|
||||
# except Exception as e:
|
||||
# diagnose_error(e)
|
||||
```
|
||||
|
||||
### System Health Check
|
||||
|
||||
```python
|
||||
def health_check():
|
||||
"""Comprehensive system health check"""
|
||||
print("=" * 50)
|
||||
print("SYSTEM HEALTH CHECK")
|
||||
print("=" * 50)
|
||||
|
||||
issues = []
|
||||
|
||||
# Memory check
|
||||
gc.collect()
|
||||
free_mem = gc.mem_free()
|
||||
if free_mem < 10000:
|
||||
issues.append("CRITICAL: Very low memory")
|
||||
elif free_mem < 50000:
|
||||
issues.append("WARNING: Low memory")
|
||||
else:
|
||||
print("✓ Memory: OK")
|
||||
|
||||
# Filesystem check
|
||||
try:
|
||||
stat = os.statvfs('/')
|
||||
free_blocks = stat[3]
|
||||
block_size = stat[0]
|
||||
free_bytes = free_blocks * block_size
|
||||
|
||||
if free_bytes < 100000:
|
||||
issues.append("WARNING: Low disk space")
|
||||
else:
|
||||
print("✓ Filesystem: OK")
|
||||
except:
|
||||
issues.append("ERROR: Cannot check filesystem")
|
||||
|
||||
# Core modules check
|
||||
required = ['badger2040', 'machine', 'time', 'gc', 'sys', 'os']
|
||||
for module in required:
|
||||
try:
|
||||
__import__(module)
|
||||
except:
|
||||
issues.append(f"ERROR: Missing module '{module}'")
|
||||
|
||||
if not issues:
|
||||
print("✓ Core modules: OK")
|
||||
|
||||
# Display results
|
||||
if issues:
|
||||
print("\n" + "!" * 50)
|
||||
print("ISSUES FOUND:")
|
||||
for issue in issues:
|
||||
print(f" {issue}")
|
||||
print("!" * 50)
|
||||
else:
|
||||
print("\n" + "=" * 50)
|
||||
print("✓ ALL SYSTEMS HEALTHY")
|
||||
print("=" * 50)
|
||||
|
||||
health_check()
|
||||
```
|
||||
|
||||
## Recovery Procedures
|
||||
|
||||
### Safe Mode Boot
|
||||
|
||||
If badge won't boot normally:
|
||||
|
||||
1. **Hold BOOTSEL button** while connecting USB
|
||||
2. Badge appears as USB drive
|
||||
3. Delete `main.py` if it's causing crashes
|
||||
4. Copy new firmware `.uf2` file to drive
|
||||
5. Badge will reboot automatically
|
||||
|
||||
### Factory Reset
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
def factory_reset():
|
||||
"""Remove all user files (DANGEROUS!)"""
|
||||
print("WARNING: This will delete all files!")
|
||||
print("Type 'CONFIRM' to proceed:")
|
||||
|
||||
# In interactive mode, get user confirmation
|
||||
# confirm = input()
|
||||
# if confirm != 'CONFIRM':
|
||||
# print("Reset cancelled")
|
||||
# return
|
||||
|
||||
print("Removing files...")
|
||||
for f in os.listdir('/'):
|
||||
if f not in ['boot.py']: # Keep boot.py
|
||||
try:
|
||||
os.remove(f)
|
||||
print(f" Removed {f}")
|
||||
except:
|
||||
pass
|
||||
|
||||
print("Factory reset complete. Reboot badge.")
|
||||
|
||||
# Uncomment to use:
|
||||
# factory_reset()
|
||||
```
|
||||
|
||||
### Firmware Reflash
|
||||
|
||||
```bash
|
||||
# From your computer (badge in BOOTSEL mode)
|
||||
|
||||
# Download latest MicroPython firmware
|
||||
# Visit: https://micropython.org/download/rp2-pico-w/
|
||||
|
||||
# Flash firmware
|
||||
# Drag .uf2 file to RPI-RP2 drive
|
||||
# Or use picotool:
|
||||
picotool load firmware.uf2
|
||||
|
||||
# Verify
|
||||
picotool info
|
||||
```
|
||||
|
||||
## Troubleshooting Checklist
|
||||
|
||||
When encountering issues, work through this checklist:
|
||||
|
||||
- [ ] Check MicroPython version (`sys.version`)
|
||||
- [ ] Verify core modules load (`import badger2040`)
|
||||
- [ ] Run memory diagnostic (`gc.mem_free()`)
|
||||
- [ ] Check filesystem space (`os.statvfs('/')`)
|
||||
- [ ] Test display (`badge.update()`)
|
||||
- [ ] Test buttons (button test function)
|
||||
- [ ] Scan I2C bus (if using sensors)
|
||||
- [ ] Test WiFi connection (if using network)
|
||||
- [ ] Review error messages carefully
|
||||
- [ ] Check documentation for API changes
|
||||
- [ ] Try soft reset (Ctrl+D in REPL)
|
||||
- [ ] Try hard reset (power cycle)
|
||||
|
||||
This comprehensive diagnostic approach will help you quickly identify and resolve issues with your Badger 2350!
|
||||
527
skills/badger-hardware/SKILL.md
Normal file
527
skills/badger-hardware/SKILL.md
Normal file
@@ -0,0 +1,527 @@
|
||||
---
|
||||
name: badger-hardware
|
||||
description: Hardware integration for Badger 2350 including GPIO, I2C sensors, SPI devices, and electronic components. Use when connecting external hardware, working with sensors, controlling LEDs, reading buttons, or interfacing with I2C/SPI devices on Badger 2350.
|
||||
---
|
||||
|
||||
# Badger 2350 Hardware Integration
|
||||
|
||||
Interface with GPIO pins, sensors, and external hardware on the Badger 2350 badge using I2C, SPI, and digital I/O.
|
||||
|
||||
## GPIO Basics
|
||||
|
||||
### Pin Configuration
|
||||
|
||||
```python
|
||||
from machine import Pin
|
||||
|
||||
# Configure pin as output (LED, relay, etc.)
|
||||
led = Pin(25, Pin.OUT)
|
||||
led.value(1) # Turn on (HIGH)
|
||||
led.value(0) # Turn off (LOW)
|
||||
led.toggle() # Toggle state
|
||||
|
||||
# Configure pin as input (button, switch, etc.)
|
||||
button = Pin(15, Pin.IN, Pin.PULL_UP)
|
||||
if button.value() == 0: # Button pressed (pulled to ground)
|
||||
print("Button pressed!")
|
||||
|
||||
# Configure pin as input with pull-down
|
||||
sensor = Pin(16, Pin.IN, Pin.PULL_DOWN)
|
||||
```
|
||||
|
||||
### PWM (Pulse Width Modulation)
|
||||
|
||||
```python
|
||||
from machine import Pin, PWM
|
||||
|
||||
# Control LED brightness or servo motor
|
||||
pwm = PWM(Pin(25))
|
||||
pwm.freq(1000) # Set frequency to 1kHz
|
||||
|
||||
# Set duty cycle (0-65535, where 65535 is 100%)
|
||||
pwm.duty_u16(32768) # 50% brightness
|
||||
pwm.duty_u16(16384) # 25% brightness
|
||||
pwm.duty_u16(65535) # 100% brightness
|
||||
|
||||
# Cleanup
|
||||
pwm.deinit()
|
||||
```
|
||||
|
||||
### Interrupts
|
||||
|
||||
```python
|
||||
from machine import Pin
|
||||
|
||||
button = Pin(15, Pin.IN, Pin.PULL_UP)
|
||||
|
||||
def button_callback(pin):
|
||||
print(f"Button pressed! Pin: {pin}")
|
||||
|
||||
# Trigger on falling edge (button press)
|
||||
button.irq(trigger=Pin.IRQ_FALLING, handler=button_callback)
|
||||
|
||||
# Trigger on rising edge (button release)
|
||||
button.irq(trigger=Pin.IRQ_RISING, handler=button_callback)
|
||||
|
||||
# Trigger on both edges
|
||||
button.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=button_callback)
|
||||
```
|
||||
|
||||
## I2C Communication
|
||||
|
||||
### I2C Setup
|
||||
|
||||
```python
|
||||
from machine import I2C, Pin
|
||||
|
||||
# Initialize I2C (QWIIC connector uses specific pins)
|
||||
i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
|
||||
|
||||
# Scan for connected devices
|
||||
devices = i2c.scan()
|
||||
print(f"Found {len(devices)} I2C devices:")
|
||||
for device in devices:
|
||||
print(f" Address: 0x{device:02x}")
|
||||
```
|
||||
|
||||
### Reading from I2C Device
|
||||
|
||||
```python
|
||||
# Read data from I2C device
|
||||
address = 0x48 # Example: Temperature sensor
|
||||
data = i2c.readfrom(address, 2) # Read 2 bytes
|
||||
print(f"Raw data: {data}")
|
||||
|
||||
# Read from specific register
|
||||
register = 0x00
|
||||
i2c.writeto(address, bytes([register])) # Select register
|
||||
data = i2c.readfrom(address, 2) # Read data
|
||||
```
|
||||
|
||||
### Writing to I2C Device
|
||||
|
||||
```python
|
||||
# Write single byte
|
||||
address = 0x48
|
||||
data = bytes([0x01, 0xA0])
|
||||
i2c.writeto(address, data)
|
||||
|
||||
# Write to specific register
|
||||
register = 0x01
|
||||
value = 0xFF
|
||||
i2c.writeto(address, bytes([register, value]))
|
||||
```
|
||||
|
||||
## Common I2C Sensors
|
||||
|
||||
### BME280 (Temperature, Humidity, Pressure)
|
||||
|
||||
```python
|
||||
from machine import I2C, Pin
|
||||
import time
|
||||
|
||||
class BME280:
|
||||
def __init__(self, i2c, address=0x76):
|
||||
self.i2c = i2c
|
||||
self.address = address
|
||||
|
||||
def read_temp(self):
|
||||
# Read temperature register
|
||||
data = self.i2c.readfrom_mem(self.address, 0xFA, 3)
|
||||
temp_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
|
||||
# Apply calibration (simplified)
|
||||
temp_c = temp_raw / 100.0
|
||||
return temp_c
|
||||
|
||||
def read_humidity(self):
|
||||
# Read humidity register
|
||||
data = self.i2c.readfrom_mem(self.address, 0xFD, 2)
|
||||
hum_raw = (data[0] << 8) | data[1]
|
||||
humidity = hum_raw / 1024.0
|
||||
return humidity
|
||||
|
||||
# Usage
|
||||
i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
|
||||
sensor = BME280(i2c)
|
||||
|
||||
temp = sensor.read_temp()
|
||||
humidity = sensor.read_humidity()
|
||||
print(f"Temp: {temp:.1f}°C, Humidity: {humidity:.1f}%")
|
||||
```
|
||||
|
||||
### APDS9960 (Gesture, Proximity, Color Sensor)
|
||||
|
||||
```python
|
||||
class APDS9960:
|
||||
def __init__(self, i2c, address=0x39):
|
||||
self.i2c = i2c
|
||||
self.address = address
|
||||
self._init_sensor()
|
||||
|
||||
def _init_sensor(self):
|
||||
# Enable device
|
||||
self.i2c.writeto_mem(self.address, 0x80, bytes([0x01]))
|
||||
# Enable gesture mode
|
||||
self.i2c.writeto_mem(self.address, 0x93, bytes([0x01]))
|
||||
|
||||
def read_gesture(self):
|
||||
# Read gesture FIFO
|
||||
fifo_level = self.i2c.readfrom_mem(self.address, 0xAE, 1)[0]
|
||||
if fifo_level > 0:
|
||||
data = self.i2c.readfrom_mem(self.address, 0xFC, 4)
|
||||
# Process gesture data
|
||||
return self._detect_gesture(data)
|
||||
return None
|
||||
|
||||
def _detect_gesture(self, data):
|
||||
# Simplified gesture detection
|
||||
if data[0] > data[2]:
|
||||
return "UP"
|
||||
elif data[0] < data[2]:
|
||||
return "DOWN"
|
||||
elif data[1] > data[3]:
|
||||
return "LEFT"
|
||||
else:
|
||||
return "RIGHT"
|
||||
|
||||
# Usage
|
||||
i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
|
||||
gesture_sensor = APDS9960(i2c)
|
||||
|
||||
gesture = gesture_sensor.read_gesture()
|
||||
if gesture:
|
||||
print(f"Gesture detected: {gesture}")
|
||||
```
|
||||
|
||||
### VL53L0X (Time-of-Flight Distance Sensor)
|
||||
|
||||
```python
|
||||
class VL53L0X:
|
||||
def __init__(self, i2c, address=0x29):
|
||||
self.i2c = i2c
|
||||
self.address = address
|
||||
|
||||
def read_distance(self):
|
||||
# Start measurement
|
||||
self.i2c.writeto_mem(self.address, 0x00, bytes([0x01]))
|
||||
|
||||
# Wait for measurement
|
||||
time.sleep(0.05)
|
||||
|
||||
# Read distance (mm)
|
||||
data = self.i2c.readfrom_mem(self.address, 0x14, 2)
|
||||
distance = (data[0] << 8) | data[1]
|
||||
return distance
|
||||
|
||||
# Usage
|
||||
i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
|
||||
tof = VL53L0X(i2c)
|
||||
|
||||
distance = tof.read_distance()
|
||||
print(f"Distance: {distance}mm")
|
||||
```
|
||||
|
||||
## SPI Communication
|
||||
|
||||
### SPI Setup
|
||||
|
||||
```python
|
||||
from machine import SPI, Pin
|
||||
|
||||
# Initialize SPI
|
||||
spi = SPI(0, baudrate=1000000, polarity=0, phase=0,
|
||||
sck=Pin(2), mosi=Pin(3), miso=Pin(4))
|
||||
|
||||
# Chip select pin
|
||||
cs = Pin(5, Pin.OUT)
|
||||
cs.value(1) # Deselect initially
|
||||
|
||||
# Read/write data
|
||||
cs.value(0) # Select device
|
||||
spi.write(bytes([0x01, 0x02, 0x03])) # Write data
|
||||
data = spi.read(3) # Read 3 bytes
|
||||
cs.value(1) # Deselect device
|
||||
|
||||
print(f"Received: {data}")
|
||||
```
|
||||
|
||||
### SD Card Reader (SPI)
|
||||
|
||||
```python
|
||||
from machine import SPI, Pin
|
||||
import sdcard
|
||||
import os
|
||||
|
||||
# Initialize SPI and SD card
|
||||
spi = SPI(0, baudrate=1000000, sck=Pin(2), mosi=Pin(3), miso=Pin(4))
|
||||
cs = Pin(5, Pin.OUT)
|
||||
|
||||
sd = sdcard.SDCard(spi, cs)
|
||||
|
||||
# Mount SD card
|
||||
os.mount(sd, '/sd')
|
||||
|
||||
# Write file
|
||||
with open('/sd/data.txt', 'w') as f:
|
||||
f.write('Hello from Badger!')
|
||||
|
||||
# Read file
|
||||
with open('/sd/data.txt', 'r') as f:
|
||||
print(f.read())
|
||||
|
||||
# Unmount
|
||||
os.umount('/sd')
|
||||
```
|
||||
|
||||
## Analog Input (ADC)
|
||||
|
||||
```python
|
||||
from machine import ADC, Pin
|
||||
|
||||
# Initialize ADC on GPIO pin
|
||||
adc = ADC(Pin(26))
|
||||
|
||||
# Read raw value (0-65535 for 16-bit ADC)
|
||||
raw_value = adc.read_u16()
|
||||
print(f"Raw ADC: {raw_value}")
|
||||
|
||||
# Convert to voltage (assuming 3.3V reference)
|
||||
voltage = (raw_value / 65535) * 3.3
|
||||
print(f"Voltage: {voltage:.2f}V")
|
||||
|
||||
# Example: Read potentiometer
|
||||
while True:
|
||||
value = adc.read_u16()
|
||||
percentage = (value / 65535) * 100
|
||||
print(f"Pot: {percentage:.1f}%")
|
||||
time.sleep(0.1)
|
||||
```
|
||||
|
||||
## NeoPixel (WS2812B) LEDs
|
||||
|
||||
```python
|
||||
from machine import Pin
|
||||
import neopixel
|
||||
import time
|
||||
|
||||
# Initialize NeoPixel strip (8 LEDs on pin 25)
|
||||
num_leds = 8
|
||||
np = neopixel.NeoPixel(Pin(25), num_leds)
|
||||
|
||||
# Set individual LED color (R, G, B)
|
||||
np[0] = (255, 0, 0) # Red
|
||||
np[1] = (0, 255, 0) # Green
|
||||
np[2] = (0, 0, 255) # Blue
|
||||
np[3] = (255, 255, 0) # Yellow
|
||||
np.write() # Update LEDs
|
||||
|
||||
# Rainbow effect
|
||||
def rainbow_cycle(wait):
|
||||
for j in range(255):
|
||||
for i in range(num_leds):
|
||||
pixel_index = (i * 256 // num_leds) + j
|
||||
np[i] = wheel(pixel_index & 255)
|
||||
np.write()
|
||||
time.sleep(wait)
|
||||
|
||||
def wheel(pos):
|
||||
"""Generate rainbow colors across 0-255 positions"""
|
||||
if pos < 85:
|
||||
return (pos * 3, 255 - pos * 3, 0)
|
||||
elif pos < 170:
|
||||
pos -= 85
|
||||
return (255 - pos * 3, 0, pos * 3)
|
||||
else:
|
||||
pos -= 170
|
||||
return (0, pos * 3, 255 - pos * 3)
|
||||
|
||||
rainbow_cycle(0.001)
|
||||
```
|
||||
|
||||
## Servo Motor Control
|
||||
|
||||
```python
|
||||
from machine import Pin, PWM
|
||||
import time
|
||||
|
||||
class Servo:
|
||||
def __init__(self, pin):
|
||||
self.pwm = PWM(Pin(pin))
|
||||
self.pwm.freq(50) # 50Hz for servo
|
||||
|
||||
def angle(self, degrees):
|
||||
"""Set servo angle (0-180 degrees)"""
|
||||
# Convert angle to duty cycle
|
||||
# 0° = 1ms (3.2% duty)
|
||||
# 90° = 1.5ms (7.5% duty)
|
||||
# 180° = 2ms (10% duty)
|
||||
min_duty = 1638 # 2.5% of 65535
|
||||
max_duty = 8192 # 12.5% of 65535
|
||||
duty = int(min_duty + (degrees / 180) * (max_duty - min_duty))
|
||||
self.pwm.duty_u16(duty)
|
||||
|
||||
def deinit(self):
|
||||
self.pwm.deinit()
|
||||
|
||||
# Usage
|
||||
servo = Servo(25)
|
||||
|
||||
# Sweep servo
|
||||
for angle in range(0, 181, 10):
|
||||
servo.angle(angle)
|
||||
time.sleep(0.1)
|
||||
|
||||
servo.deinit()
|
||||
```
|
||||
|
||||
## Relay Control
|
||||
|
||||
```python
|
||||
from machine import Pin
|
||||
import time
|
||||
|
||||
class Relay:
|
||||
def __init__(self, pin):
|
||||
self.pin = Pin(pin, Pin.OUT)
|
||||
self.off()
|
||||
|
||||
def on(self):
|
||||
self.pin.value(1)
|
||||
|
||||
def off(self):
|
||||
self.pin.value(0)
|
||||
|
||||
def toggle(self):
|
||||
self.pin.toggle()
|
||||
|
||||
# Usage
|
||||
relay = Relay(25)
|
||||
|
||||
relay.on()
|
||||
time.sleep(2)
|
||||
relay.off()
|
||||
|
||||
# Pulse relay
|
||||
for i in range(5):
|
||||
relay.toggle()
|
||||
time.sleep(0.5)
|
||||
```
|
||||
|
||||
## Integration with Badge Display
|
||||
|
||||
### Display Sensor Data
|
||||
|
||||
```python
|
||||
import badger2040
|
||||
from machine import I2C, Pin
|
||||
import time
|
||||
|
||||
badge = badger2040.Badger2040()
|
||||
i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
|
||||
|
||||
def display_sensor_data():
|
||||
# Read sensor (example)
|
||||
temp = read_temperature() # Your sensor function
|
||||
humidity = read_humidity()
|
||||
|
||||
badge.set_pen(15)
|
||||
badge.clear()
|
||||
badge.set_pen(0)
|
||||
|
||||
badge.text("Sensor Monitor", 10, 10, scale=2)
|
||||
badge.text(f"Temp: {temp:.1f}C", 10, 40, scale=2)
|
||||
badge.text(f"Humidity: {humidity:.1f}%", 10, 70, scale=2)
|
||||
|
||||
badge.update()
|
||||
|
||||
while True:
|
||||
display_sensor_data()
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
### Interactive Hardware Control
|
||||
|
||||
```python
|
||||
import badger2040
|
||||
from machine import Pin
|
||||
import time
|
||||
|
||||
badge = badger2040.Badger2040()
|
||||
led = Pin(25, Pin.OUT)
|
||||
led_state = False
|
||||
|
||||
def draw_ui():
|
||||
badge.set_pen(15)
|
||||
badge.clear()
|
||||
badge.set_pen(0)
|
||||
|
||||
badge.text("LED Control", 10, 10, scale=2)
|
||||
|
||||
status = "ON" if led_state else "OFF"
|
||||
badge.text(f"Status: {status}", 10, 50, scale=2)
|
||||
|
||||
badge.text("A: Toggle", 10, 100, scale=1)
|
||||
|
||||
badge.update()
|
||||
|
||||
while True:
|
||||
draw_ui()
|
||||
|
||||
if badge.pressed(badger2040.BUTTON_A):
|
||||
led_state = not led_state
|
||||
led.value(1 if led_state else 0)
|
||||
time.sleep(0.2) # Debounce
|
||||
```
|
||||
|
||||
## Power Management for Hardware
|
||||
|
||||
```python
|
||||
from machine import Pin
|
||||
import machine
|
||||
|
||||
# Power control for external hardware
|
||||
power_pin = Pin(23, Pin.OUT)
|
||||
|
||||
def power_on():
|
||||
power_pin.value(1)
|
||||
|
||||
def power_off():
|
||||
power_pin.value(0)
|
||||
|
||||
# Use with sleep modes
|
||||
power_on()
|
||||
# ... do work with sensor ...
|
||||
power_off()
|
||||
machine.lightsleep(5000) # Sleep 5 seconds
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**I2C device not detected**: Check wiring, verify device address, ensure pull-up resistors are present
|
||||
|
||||
**GPIO not working**: Verify pin is not used by internal badge functions, check if pin is input/output capable
|
||||
|
||||
**SPI communication fails**: Check clock polarity and phase, verify baudrate is within device specs
|
||||
|
||||
**PWM not smooth**: Increase PWM frequency, ensure duty cycle calculations are correct
|
||||
|
||||
**Sensor readings unstable**: Add delays between readings, use averaging, check power supply stability
|
||||
|
||||
## Hardware Safety
|
||||
|
||||
- **Never exceed 3.3V** on GPIO pins
|
||||
- Use level shifters for 5V devices
|
||||
- Add current-limiting resistors for LEDs
|
||||
- Use flyback diodes with motors and relays
|
||||
- Keep I2C wires short (< 20cm) or use bus extenders
|
||||
- Use proper power supply for high-current devices
|
||||
|
||||
## Pinout Reference
|
||||
|
||||
Common Badger 2350 pins available for external hardware:
|
||||
- GPIO 0-22: General purpose I/O
|
||||
- GPIO 26-28: ADC capable (analog input)
|
||||
- I2C QWIIC: SCL (Pin 5), SDA (Pin 4)
|
||||
- SPI: SCK, MOSI, MISO (check documentation)
|
||||
|
||||
Refer to official Badger 2350 pinout diagram for complete details.
|
||||
587
skills/badger-quickstart/SKILL.md
Normal file
587
skills/badger-quickstart/SKILL.md
Normal file
@@ -0,0 +1,587 @@
|
||||
---
|
||||
name: badger-quickstart
|
||||
description: Complete getting started guide for Universe 2025 (Tufty) Badge from zero to first app. Use when helping absolute beginners, providing step-by-step first-time setup, or when users ask "how do I get started", "where do I begin", or "first steps with the badge".
|
||||
---
|
||||
|
||||
# Universe 2025 (Tufty) Badge Quickstart Guide
|
||||
|
||||
Complete step-by-step guide to go from zero to creating your first app for **MonaOS** on the Universe 2025 (Tufty) Badge. Perfect for absolute beginners!
|
||||
|
||||
## About the Badge
|
||||
|
||||
The Universe 2025 Badge is a custom version of the Pimoroni Tufty 2350, created for GitHub Universe 2025. It comes pre-loaded with **MonaOS**, a MicroPython-based operating system with an app launcher.
|
||||
|
||||
### Hardware Specifications
|
||||
- **RP2350** Dual-core ARM Cortex-M33 @ 200MHz
|
||||
- **512kB SRAM** and **16MB QSPI XiP flash**
|
||||
- **320x240 full colour IPS display** (framebuffer pixel doubled from 160x120)
|
||||
- **2.4GHz WiFi and Bluetooth 5**
|
||||
- **1000mAh rechargeable battery** (up to 8 hours runtime)
|
||||
- **IR receiver and transmitter** for beacon hunting
|
||||
- **Five front-facing buttons** (A, B, C, UP, DOWN)
|
||||
- **4-zone LED backlight**
|
||||
- **USB-C** for charging and programming
|
||||
|
||||
## About MonaOS
|
||||
|
||||
Your badge runs **MonaOS**, which provides:
|
||||
- An app launcher that auto-discovers apps in `/system/apps/`
|
||||
- Each app is a directory containing `__init__.py`, `icon.png` (24x24), and optional `assets/`
|
||||
- Apps implement `update()` function called every frame
|
||||
- Navigate apps using physical buttons
|
||||
|
||||
## What You'll Need
|
||||
|
||||
### Hardware
|
||||
- ✓ Universe 2025 (Tufty) Badge
|
||||
- ✓ USB-C cable (data capable, not just charging)
|
||||
- ✓ Computer (macOS, Linux, or Windows)
|
||||
|
||||
### Software (we'll install together)
|
||||
- Python 3.8 or newer
|
||||
- Development tools (mpremote)
|
||||
- Text editor or IDE
|
||||
|
||||
### Time Required
|
||||
- First-time setup: 30-45 minutes
|
||||
- Your first app: 20-30 minutes
|
||||
|
||||
## Step-by-Step Setup
|
||||
|
||||
### Step 1: Install Python on Your Computer
|
||||
|
||||
**Why**: You need Python on your computer to run the tools that communicate with your badge.
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
# Install using Homebrew
|
||||
brew install python3
|
||||
|
||||
# Verify
|
||||
python3 --version # Should show 3.8 or higher
|
||||
```
|
||||
|
||||
**Linux (Ubuntu/Debian):**
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-venv
|
||||
python3 --version
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
1. Download from https://www.python.org/downloads/
|
||||
2. Run installer
|
||||
3. ✓ CHECK "Add Python to PATH"
|
||||
4. Complete installation
|
||||
5. Open PowerShell and verify: `python --version`
|
||||
|
||||
✅ **Checkpoint**: Run `python3 --version` (or `python --version` on Windows). You should see version 3.8 or higher.
|
||||
|
||||
> **Need help?** See the `python-setup` skill for detailed installation guides.
|
||||
|
||||
### Step 2: Create Your First Project
|
||||
|
||||
**Why**: Keeping projects organized and using virtual environments prevents conflicts.
|
||||
|
||||
```bash
|
||||
# Create a directory for your project
|
||||
mkdir ~/badger-projects
|
||||
cd ~/badger-projects
|
||||
mkdir hello-badge
|
||||
cd hello-badge
|
||||
|
||||
# Create a virtual environment
|
||||
python3 -m venv venv
|
||||
|
||||
# Activate it
|
||||
# macOS/Linux:
|
||||
source venv/bin/activate
|
||||
|
||||
# Windows (PowerShell):
|
||||
venv\Scripts\Activate.ps1
|
||||
|
||||
# Windows (Command Prompt):
|
||||
venv\Scripts\activate.bat
|
||||
```
|
||||
|
||||
✅ **Checkpoint**: Your terminal prompt should now show `(venv)` at the beginning.
|
||||
|
||||
### Step 3: Install Badge Tools
|
||||
|
||||
**Why**: These tools let you communicate with your badge, upload files, and run code.
|
||||
|
||||
```bash
|
||||
# Make sure venv is activated (you should see "(venv)" in prompt)
|
||||
|
||||
# Install essential tool
|
||||
pip install mpremote
|
||||
|
||||
# Verify installation
|
||||
mpremote --version
|
||||
```
|
||||
|
||||
✅ **Checkpoint**: Command should show version number without errors.
|
||||
|
||||
### Step 4: Connect Your Badge
|
||||
|
||||
**Why**: Let's make sure your computer can talk to your badge.
|
||||
|
||||
1. **Connect badge to computer** using USB-C cable
|
||||
2. **Find the device**:
|
||||
|
||||
**macOS/Linux:**
|
||||
```bash
|
||||
ls /dev/tty.usb*
|
||||
# Should see something like: /dev/tty.usbmodem14201
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
```powershell
|
||||
# In PowerShell
|
||||
[System.IO.Ports.SerialPort]::getportnames()
|
||||
# Should see something like: COM3
|
||||
```
|
||||
|
||||
3. **Test connection**:
|
||||
|
||||
**macOS/Linux:**
|
||||
```bash
|
||||
mpremote connect /dev/tty.usbmodem* exec "print('Hello from Badge!')"
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
```powershell
|
||||
mpremote connect COM3 exec "print('Hello from Badge!')"
|
||||
```
|
||||
|
||||
✅ **Checkpoint**: You should see "Hello from Badge!" printed in your terminal.
|
||||
|
||||
> **Troubleshooting**:
|
||||
> - Badge not found? Try different USB ports
|
||||
> - Permission denied? See Step 4a below
|
||||
> - Still stuck? See the `badger-diagnostics` skill
|
||||
|
||||
### Step 4a: Fix Permissions (Linux only)
|
||||
|
||||
If you get "Permission denied" on Linux:
|
||||
|
||||
```bash
|
||||
# Add yourself to dialout group
|
||||
sudo usermod -a -G dialout $USER
|
||||
|
||||
# Log out and log back in for changes to take effect
|
||||
# Or restart your computer
|
||||
```
|
||||
|
||||
### Step 5: Verify Badge is Ready (CRITICAL)
|
||||
|
||||
**Why**: We must verify everything is working before writing code.
|
||||
|
||||
```bash
|
||||
# macOS/Linux:
|
||||
mpremote connect /dev/tty.usbmodem* exec "
|
||||
import sys
|
||||
print('✓ MicroPython:', sys.version)
|
||||
|
||||
# Test badgeware module
|
||||
from badgeware import screen, brushes, shapes
|
||||
print('✓ badgeware module: loaded')
|
||||
print('✓ Display size: 160x120')
|
||||
print('✓ ALL CHECKS PASSED!')
|
||||
"
|
||||
|
||||
# Windows:
|
||||
mpremote connect COM3 exec "import sys; from badgeware import screen, brushes, shapes; print('✓ MicroPython:', sys.version); print('✓ badgeware loaded'); print('✓ ALL CHECKS PASSED!')"
|
||||
```
|
||||
|
||||
✅ **Checkpoint - ALL must pass**:
|
||||
- ✓ MicroPython version shown
|
||||
- ✓ badgeware module loads
|
||||
- ✓ No error messages
|
||||
|
||||
**DO NOT PROCEED until all checks pass.**
|
||||
|
||||
### Step 6: Understand MonaOS App Structure
|
||||
|
||||
MonaOS apps must follow this structure:
|
||||
|
||||
```
|
||||
my_app/
|
||||
├── icon.png # 24x24 PNG icon for launcher
|
||||
├── __init__.py # Entry point with update() function
|
||||
└── assets/ # Optional: app assets (auto-added to path)
|
||||
└── ...
|
||||
```
|
||||
|
||||
Your `__init__.py` must implement:
|
||||
- **`init()`** - Optional, called once when app launches
|
||||
- **`update()`** - Required, called every frame by MonaOS
|
||||
- **`on_exit()`** - Optional, called when returning to menu
|
||||
|
||||
### Step 7: Create Your First App
|
||||
|
||||
Create the app directory structure:
|
||||
|
||||
```bash
|
||||
mkdir hello_app
|
||||
cd hello_app
|
||||
```
|
||||
|
||||
Create `hello_app/__init__.py`:
|
||||
|
||||
```python
|
||||
# hello_app/__init__.py - Your first MonaOS app!
|
||||
from badgeware import screen, brushes, shapes, io, PixelFont
|
||||
import math
|
||||
|
||||
# Optional: called once when app launches
|
||||
def init():
|
||||
screen.font = PixelFont.load("nope.ppf")
|
||||
print("Hello app initialized!")
|
||||
|
||||
# Required: called every frame by MonaOS
|
||||
def update():
|
||||
# Clear the framebuffer
|
||||
screen.brush = brushes.color(20, 40, 60)
|
||||
screen.clear()
|
||||
|
||||
# Draw animated sine wave
|
||||
y = (math.sin(io.ticks / 100) * 20) + 60
|
||||
screen.brush = brushes.color(0, 255, 0)
|
||||
for x in range(160):
|
||||
screen.draw(shapes.rectangle(x, int(y), 1, 1))
|
||||
|
||||
# Draw text
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.text("Hello, Badge!", 10, 10)
|
||||
screen.text("Press HOME to exit", 10, 100)
|
||||
|
||||
# Handle button presses
|
||||
if io.BUTTON_A in io.pressed:
|
||||
print("Button A pressed!")
|
||||
if io.BUTTON_HOME in io.pressed:
|
||||
# HOME button exits to MonaOS menu automatically
|
||||
pass
|
||||
|
||||
# Optional: called before returning to menu
|
||||
def on_exit():
|
||||
print("App exiting!")
|
||||
```
|
||||
|
||||
Create `hello_app/icon.png`:
|
||||
- 24x24 pixel PNG image
|
||||
- Use any image editor to create a simple icon
|
||||
- Or download a free icon and resize it
|
||||
|
||||
✅ **Checkpoint**: Files created:
|
||||
- `hello_app/__init__.py`
|
||||
- `hello_app/icon.png` (24x24 PNG)
|
||||
|
||||
### Step 8: Test Your App Locally
|
||||
|
||||
```bash
|
||||
# From your project directory (not inside hello_app/)
|
||||
cd ~/badger-projects/hello-badge
|
||||
|
||||
# Run the app temporarily (doesn't save to badge)
|
||||
# macOS/Linux:
|
||||
mpremote connect /dev/tty.usbmodem* run hello_app/__init__.py
|
||||
|
||||
# Windows:
|
||||
mpremote connect COM3 run hello_app/__init__.py
|
||||
```
|
||||
|
||||
✅ **Checkpoint**: Your badge display should show "Hello, Badge!" with an animated wave. Press HOME to exit.
|
||||
|
||||
### Step 9: Install Your App to MonaOS
|
||||
|
||||
**Why**: Install it permanently so it appears in the MonaOS launcher menu!
|
||||
|
||||
**⚠️ IMPORTANT**: The `/system/apps/` directory is READ-ONLY via mpremote. You MUST use USB Mass Storage Mode.
|
||||
|
||||
#### Enter USB Mass Storage Mode
|
||||
|
||||
1. **Connect badge** via USB-C (if not already connected)
|
||||
2. **Press RESET button TWICE** quickly (double-click the RESET button on the back)
|
||||
3. **Wait 2-3 seconds** - Badge will appear as **"BADGER"** drive
|
||||
4. **Verify**: Drive should appear in Finder (macOS), File Explorer (Windows), or file manager (Linux)
|
||||
|
||||
#### Install Your App
|
||||
|
||||
**macOS/Linux:**
|
||||
```bash
|
||||
# Copy your entire app directory to the badge
|
||||
cp -r hello_app /Volumes/BADGER/apps/
|
||||
|
||||
# OR manually via Finder:
|
||||
# 1. Open BADGER drive in Finder
|
||||
# 2. Navigate to apps/ folder
|
||||
# 3. Drag hello_app folder into apps/
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
```powershell
|
||||
# Copy your entire app directory
|
||||
# (Replace D: with your actual BADGER drive letter)
|
||||
xcopy hello_app D:\apps\hello_app\ /E /I
|
||||
|
||||
# OR manually via File Explorer:
|
||||
# 1. Open BADGER drive
|
||||
# 2. Navigate to apps\ folder
|
||||
# 3. Drag hello_app folder into apps\
|
||||
```
|
||||
|
||||
#### Exit Mass Storage Mode
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
# Eject the drive
|
||||
diskutil eject /Volumes/BADGER
|
||||
|
||||
# Or right-click BADGER in Finder → Eject
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
- Right-click BADGER drive → "Eject"
|
||||
- Or use "Safely Remove Hardware" in system tray
|
||||
|
||||
**Linux:**
|
||||
```bash
|
||||
# Eject the drive
|
||||
sudo umount /media/$USER/BADGER
|
||||
```
|
||||
|
||||
**All Platforms:**
|
||||
- Press **RESET button once** on the badge
|
||||
- Badge reboots into MonaOS with your app installed!
|
||||
|
||||
✅ **Checkpoint**:
|
||||
- BADGER drive was successfully ejected
|
||||
- Badge reboots normally (you see MonaOS menu)
|
||||
|
||||
### Step 10: Launch Your App from the Badge
|
||||
|
||||
1. **On your badge**: Press HOME if needed to return to MonaOS launcher
|
||||
2. **Navigate**: Use UP/DOWN buttons to find your app
|
||||
3. **Launch**: Press the select button to run your app!
|
||||
|
||||
**Note**: The default MonaOS menu shows 6 apps. You may need to expand pagination (see https://badger.github.io/hack/menu-pagination/)
|
||||
|
||||
🎉 **Congratulations!** Your first MonaOS app is installed and running!
|
||||
|
||||
## 🎉 Congratulations!
|
||||
|
||||
You just:
|
||||
- ✓ Set up your Python development environment
|
||||
- ✓ Installed badge communication tools
|
||||
- ✓ Connected to your badge
|
||||
- ✓ Created your first MonaOS app with proper structure
|
||||
- ✓ Installed your app into MonaOS launcher
|
||||
- ✓ Launched your custom app from the badge!
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
Save these commands - you'll use them a lot:
|
||||
|
||||
```bash
|
||||
# Activate your virtual environment
|
||||
source venv/bin/activate # macOS/Linux
|
||||
venv\Scripts\Activate.ps1 # Windows
|
||||
|
||||
# Test app temporarily (doesn't save)
|
||||
mpremote run my_app/__init__.py
|
||||
|
||||
# Install app to MonaOS launcher (USB Mass Storage Mode REQUIRED)
|
||||
# 1. Press RESET button twice on badge (enters Mass Storage Mode)
|
||||
# 2. Copy app to badge:
|
||||
cp -r my_app /Volumes/BADGER/apps/ # macOS/Linux
|
||||
xcopy my_app D:\apps\my_app\ /E /I # Windows
|
||||
# 3. Eject BADGER drive safely
|
||||
# 4. Press RESET once to reboot
|
||||
|
||||
# List files (read-only view)
|
||||
mpremote ls /system/apps
|
||||
|
||||
# Connect to REPL (interactive mode)
|
||||
mpremote
|
||||
# (Ctrl+C to interrupt, Ctrl+D to soft reset, Ctrl+X to exit)
|
||||
```
|
||||
|
||||
**⚠️ Remember**: You CANNOT use `mpremote` to install apps to `/system/apps/` - it's read-only! Always use USB Mass Storage Mode.
|
||||
|
||||
## Your First Improvements
|
||||
|
||||
### 1. Add Button Interactions
|
||||
|
||||
```python
|
||||
def update():
|
||||
screen.brush = brushes.color(20, 40, 60)
|
||||
screen.clear()
|
||||
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
|
||||
if io.BUTTON_A in io.held:
|
||||
screen.text("Button A held!", 10, 50)
|
||||
elif io.BUTTON_B in io.pressed:
|
||||
screen.text("Button B pressed!", 10, 50)
|
||||
elif io.BUTTON_C in io.released:
|
||||
screen.text("Button C released!", 10, 50)
|
||||
```
|
||||
|
||||
### 2. Draw Shapes
|
||||
|
||||
```python
|
||||
def update():
|
||||
screen.brush = brushes.color(20, 40, 60)
|
||||
screen.clear()
|
||||
|
||||
# Draw a circle
|
||||
screen.brush = brushes.color(255, 0, 0)
|
||||
screen.draw(shapes.circle(80, 60, 30))
|
||||
|
||||
# Draw a rectangle
|
||||
screen.brush = brushes.color(0, 255, 0)
|
||||
screen.draw(shapes.rectangle(10, 10, 50, 30))
|
||||
|
||||
# Draw a line
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.draw(shapes.line(0, 0, 160, 120))
|
||||
```
|
||||
|
||||
### 3. Create a Counter
|
||||
|
||||
```python
|
||||
# Add at top level
|
||||
counter = 0
|
||||
|
||||
def update():
|
||||
global counter
|
||||
|
||||
screen.brush = brushes.color(0, 0, 0)
|
||||
screen.clear()
|
||||
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.text(f"Count: {counter}", 30, 50)
|
||||
|
||||
if io.BUTTON_A in io.pressed:
|
||||
counter += 1
|
||||
if io.BUTTON_B in io.pressed:
|
||||
counter = 0
|
||||
```
|
||||
|
||||
## Common Beginner Questions
|
||||
|
||||
### Q: Do I need to activate venv every time?
|
||||
|
||||
**Yes**, activate it each time you open a new terminal.
|
||||
|
||||
### Q: What if I make a mistake in my code?
|
||||
|
||||
Edit locally, test with mpremote, then reinstall via Mass Storage Mode:
|
||||
```bash
|
||||
# 1. Edit your code locally
|
||||
# 2. Test it temporarily
|
||||
mpremote run my_app/__init__.py
|
||||
|
||||
# 3. Reinstall via Mass Storage Mode
|
||||
# - Press RESET twice (enters Mass Storage Mode)
|
||||
# - Replace files in /Volumes/BADGER/apps/my_app/
|
||||
# - Eject and press RESET once
|
||||
```
|
||||
|
||||
### Q: Can I edit code directly on the badge?
|
||||
|
||||
Yes! Enter USB Mass Storage Mode (press RESET twice). Badge appears as "BADGER" disk. Edit files in `/Volumes/BADGER/apps/` using any text editor.
|
||||
|
||||
### Q: What if my badge freezes?
|
||||
|
||||
Press the RESET button on the back of the badge.
|
||||
|
||||
### Q: How do I see errors?
|
||||
|
||||
Connect to REPL:
|
||||
```bash
|
||||
mpremote
|
||||
```
|
||||
Then test importing: `import my_app`. Errors will show in terminal.
|
||||
|
||||
### Q: How do I remove an app?
|
||||
|
||||
```bash
|
||||
mpremote rm -rf :/system/apps/my_app
|
||||
```
|
||||
|
||||
### Q: My app doesn't appear in the menu?
|
||||
|
||||
The default menu shows 6 apps. Expand pagination: https://badger.github.io/hack/menu-pagination/
|
||||
|
||||
## Troubleshooting Common Issues
|
||||
|
||||
### Badge not detected
|
||||
|
||||
1. Check USB cable (must be data cable, not just charging)
|
||||
2. Try different USB port
|
||||
3. Restart badge (press RESET)
|
||||
4. Check connection:
|
||||
- **macOS/Linux**: `ls /dev/tty.usb*`
|
||||
- **Windows**: Check Device Manager
|
||||
|
||||
### "Permission denied" error
|
||||
|
||||
**Linux**: Add yourself to dialout group (see Step 4a)
|
||||
**Windows**: Run PowerShell as Administrator
|
||||
|
||||
### "Module not found" error
|
||||
|
||||
Activate your virtual environment:
|
||||
```bash
|
||||
source venv/bin/activate # macOS/Linux
|
||||
venv\Scripts\Activate.ps1 # Windows
|
||||
```
|
||||
|
||||
### App doesn't appear in MonaOS menu
|
||||
|
||||
1. Verify upload: `mpremote ls /system/apps/my_app`
|
||||
2. Check files exist: `__init__.py` and `icon.png`
|
||||
3. Icon must be 24x24 PNG
|
||||
4. May need to expand menu pagination
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Learn More About App Development
|
||||
→ See `badger-app-creator` skill
|
||||
- Advanced button handling
|
||||
- Working with sprites and images
|
||||
- WiFi integration
|
||||
- State management and persistence
|
||||
|
||||
### Connect Hardware
|
||||
→ See `badger-hardware` skill
|
||||
- GPIO pins and sensors
|
||||
- I2C/SPI devices
|
||||
- IR transmitter/receiver
|
||||
- LED backlight control
|
||||
|
||||
### Use the REPL
|
||||
→ See `micropython-repl` skill
|
||||
- Interactive development
|
||||
- Quick testing
|
||||
- Install packages
|
||||
|
||||
### Improve Your Workflow
|
||||
→ See `badger-deploy` skill
|
||||
- Automated deployment scripts
|
||||
- Project organization
|
||||
- Multi-file apps
|
||||
|
||||
## Official Resources
|
||||
|
||||
- **Getting Started**: https://badger.github.io/get-started/
|
||||
- **Hacks & Tutorials**: https://badger.github.io/hacks/
|
||||
- **Official Apps**: https://badger.github.io/apps/
|
||||
- **Source Code**: https://github.com/badger/home/tree/main/badgerware
|
||||
- **API Docs**: https://github.com/badger/home/blob/main/badgerware/
|
||||
|
||||
## You're Ready!
|
||||
|
||||
You now have everything you need to create amazing projects with your Universe 2025 Badge. Happy coding! 🦡
|
||||
612
skills/micropython-repl/SKILL.md
Normal file
612
skills/micropython-repl/SKILL.md
Normal file
@@ -0,0 +1,612 @@
|
||||
---
|
||||
name: micropython-repl
|
||||
description: MicroPython REPL usage, package management, module inspection, and interactive debugging for Universe 2025 (Tufty) Badge. Use when installing MicroPython packages, testing code interactively, checking installed modules, or using the REPL for development.
|
||||
---
|
||||
|
||||
# MicroPython REPL and Package Management
|
||||
|
||||
Master the MicroPython REPL (Read-Eval-Print Loop) for interactive development, package management, and quick testing on the Universe 2025 (Tufty) Badge.
|
||||
|
||||
## Connecting to REPL
|
||||
|
||||
### Using screen (macOS/Linux)
|
||||
|
||||
```bash
|
||||
# Find the device
|
||||
ls /dev/tty.usb*
|
||||
|
||||
# Connect (115200 baud)
|
||||
screen /dev/tty.usbmodem* 115200
|
||||
|
||||
# Exit screen: Ctrl+A then K, then Y to confirm
|
||||
```
|
||||
|
||||
### Using mpremote
|
||||
|
||||
```bash
|
||||
# Install mpremote
|
||||
pip install mpremote
|
||||
|
||||
# Connect to REPL
|
||||
mpremote connect /dev/tty.usbmodem*
|
||||
|
||||
# Or auto-detect
|
||||
mpremote
|
||||
```
|
||||
|
||||
### Using Thonny IDE
|
||||
|
||||
1. Open Thonny
|
||||
2. Tools → Options → Interpreter
|
||||
3. Select "MicroPython (RP2040)"
|
||||
4. Choose correct port
|
||||
5. Shell window shows REPL
|
||||
|
||||
## REPL Basics
|
||||
|
||||
### Special Commands
|
||||
|
||||
```python
|
||||
# Ctrl+C - Interrupt running program
|
||||
# Ctrl+D - Soft reboot
|
||||
# Ctrl+E - Enter paste mode (for multi-line code)
|
||||
# Ctrl+B - Exit paste mode and execute
|
||||
|
||||
# Help system
|
||||
help() # General help
|
||||
help(modules) # List all modules
|
||||
help(badgeware) # Help on specific module
|
||||
|
||||
# Quick info
|
||||
import sys
|
||||
sys.implementation # MicroPython version
|
||||
sys.platform # Platform info
|
||||
```
|
||||
|
||||
### Interactive Testing
|
||||
|
||||
```python
|
||||
# Test code immediately
|
||||
>>> from badgeware import screen, display, brushes
|
||||
>>> screen.brush = brushes.color(255, 255, 255)
|
||||
>>> screen.text("Test", 10, 10, 2)
|
||||
>>> display.update()
|
||||
|
||||
# Test calculations
|
||||
>>> temp = 23.5
|
||||
>>> temp_f = temp * 9/5 + 32
|
||||
>>> print(f"{temp}C = {temp_f}F")
|
||||
|
||||
# Test GPIO
|
||||
>>> from machine import Pin
|
||||
>>> led = Pin(25, Pin.OUT)
|
||||
>>> led.toggle() # Toggle LED immediately
|
||||
```
|
||||
|
||||
### Paste Mode for Multi-line Code
|
||||
|
||||
```bash
|
||||
# Enter paste mode: Ctrl+E
|
||||
# Paste your code:
|
||||
def calculate_distance(x1, y1, x2, y2):
|
||||
import math
|
||||
dx = x2 - x1
|
||||
dy = y2 - y1
|
||||
return math.sqrt(dx*dx + dy*dy)
|
||||
|
||||
print(calculate_distance(0, 0, 3, 4))
|
||||
# Exit paste mode: Ctrl+D
|
||||
```
|
||||
|
||||
## Package Management
|
||||
|
||||
### Using mip (MicroPython Package Installer)
|
||||
|
||||
```python
|
||||
# Install package from micropython-lib
|
||||
import mip
|
||||
mip.install("urequests") # HTTP client
|
||||
mip.install("logging") # Logging module
|
||||
mip.install("umqtt.simple") # MQTT client
|
||||
|
||||
# Install from GitHub
|
||||
mip.install("github:org/repo/package.py")
|
||||
|
||||
# Install to specific location
|
||||
mip.install("urequests", target="/lib")
|
||||
```
|
||||
|
||||
### Using upip (older method)
|
||||
|
||||
```python
|
||||
# Connect to WiFi first
|
||||
import network
|
||||
wlan = network.WLAN(network.STA_IF)
|
||||
wlan.active(True)
|
||||
wlan.connect('SSID', 'password')
|
||||
|
||||
# Wait for connection
|
||||
while not wlan.isconnected():
|
||||
pass
|
||||
|
||||
# Install package
|
||||
import upip
|
||||
upip.install("micropython-logging")
|
||||
upip.install("picoweb")
|
||||
```
|
||||
|
||||
### Manual Package Installation
|
||||
|
||||
```bash
|
||||
# From your computer, copy files to badge
|
||||
mpremote cp mymodule.py :/lib/mymodule.py
|
||||
|
||||
# Or using ampy
|
||||
ampy --port /dev/tty.usbmodem* put mymodule.py /lib/mymodule.py
|
||||
|
||||
# Then use in REPL
|
||||
>>> import mymodule
|
||||
```
|
||||
|
||||
## Module Inspection
|
||||
|
||||
### List Available Modules
|
||||
|
||||
```python
|
||||
# List all built-in and installed modules
|
||||
help('modules')
|
||||
|
||||
# Check if module exists
|
||||
import sys
|
||||
'badgeware' in sys.modules # False until imported
|
||||
|
||||
import badgeware
|
||||
'badgeware' in sys.modules # True after import
|
||||
```
|
||||
|
||||
### Inspect Module Contents
|
||||
|
||||
```python
|
||||
# See what's in a module
|
||||
import badgeware
|
||||
dir(badgeware) # List all attributes
|
||||
|
||||
# Check specific attributes
|
||||
hasattr(badgeware, 'screen') # True
|
||||
hasattr(badgeware, 'brushes') # True
|
||||
|
||||
# Get function signature
|
||||
help(badgeware.screen)
|
||||
|
||||
# Explore submodules
|
||||
from badgeware import shapes
|
||||
dir(shapes) # See all shape functions
|
||||
|
||||
# View source (if available)
|
||||
import inspect
|
||||
inspect.getsource(mymodule.myfunction) # May not work on compiled modules
|
||||
```
|
||||
|
||||
### Check Module Location
|
||||
|
||||
```python
|
||||
# Find where module is located
|
||||
import badgeware
|
||||
badgeware.__file__ # Shows file path
|
||||
|
||||
# List files in system directory
|
||||
import os
|
||||
os.listdir('/system')
|
||||
os.listdir('/system/apps') # MonaOS apps
|
||||
```
|
||||
|
||||
## Interactive Debugging
|
||||
|
||||
### Quick Variable Inspection
|
||||
|
||||
```python
|
||||
# Run code and inspect
|
||||
>>> x = [1, 2, 3, 4, 5]
|
||||
>>> len(x)
|
||||
5
|
||||
>>> type(x)
|
||||
<class 'list'>
|
||||
>>> sum(x)
|
||||
15
|
||||
|
||||
# Object inspection
|
||||
>>> import badgeware
|
||||
>>> type(badgeware.screen)
|
||||
<class 'Screen'>
|
||||
>>> dir(badgeware.screen) # See all methods
|
||||
>>> dir(badgeware.brushes) # See brush functions
|
||||
>>> dir(badgeware.shapes) # See shape functions
|
||||
```
|
||||
|
||||
### Print Debugging
|
||||
|
||||
```python
|
||||
# Test function with prints
|
||||
def process_data(data):
|
||||
print(f"Input: {data}")
|
||||
result = data * 2
|
||||
print(f"Result: {result}")
|
||||
return result
|
||||
|
||||
>>> process_data(5)
|
||||
Input: 5
|
||||
Result: 10
|
||||
10
|
||||
```
|
||||
|
||||
### Exception Handling
|
||||
|
||||
```python
|
||||
# Test error handling
|
||||
>>> try:
|
||||
... 1 / 0
|
||||
... except ZeroDivisionError as e:
|
||||
... print(f"Error: {e}")
|
||||
Error: division by zero
|
||||
|
||||
# Get full traceback
|
||||
import sys
|
||||
try:
|
||||
buggy_function()
|
||||
except Exception as e:
|
||||
sys.print_exception(e)
|
||||
```
|
||||
|
||||
### Memory Debugging
|
||||
|
||||
```python
|
||||
# Check memory usage
|
||||
import gc
|
||||
gc.collect() # Run garbage collection
|
||||
gc.mem_free() # Free memory in bytes
|
||||
gc.mem_alloc() # Allocated memory
|
||||
|
||||
# Monitor memory during operation
|
||||
before = gc.mem_free()
|
||||
# ... do something
|
||||
after = gc.mem_free()
|
||||
print(f"Memory used: {before - after} bytes")
|
||||
```
|
||||
|
||||
## Useful REPL Helpers
|
||||
|
||||
### Create a helpers.py file
|
||||
|
||||
```python
|
||||
# helpers.py - Load in REPL for common tasks
|
||||
import gc
|
||||
import sys
|
||||
import os
|
||||
from machine import Pin, freq
|
||||
|
||||
def info():
|
||||
"""Display system information"""
|
||||
print(f"Platform: {sys.platform}")
|
||||
print(f"Version: {sys.version}")
|
||||
print(f"CPU Frequency: {freq()} Hz")
|
||||
print(f"Free Memory: {gc.mem_free()} bytes")
|
||||
|
||||
def ls(path='/'):
|
||||
"""List files in directory"""
|
||||
try:
|
||||
files = os.listdir(path)
|
||||
for f in files:
|
||||
print(f)
|
||||
except:
|
||||
print(f"Error listing {path}")
|
||||
|
||||
def cat(filename):
|
||||
"""Display file contents"""
|
||||
try:
|
||||
with open(filename, 'r') as f:
|
||||
print(f.read())
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
def rm(filename):
|
||||
"""Remove file"""
|
||||
try:
|
||||
os.remove(filename)
|
||||
print(f"Removed {filename}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
def blink(pin=25, times=3):
|
||||
"""Blink LED for testing"""
|
||||
import time
|
||||
led = Pin(pin, Pin.OUT)
|
||||
for i in range(times):
|
||||
led.toggle()
|
||||
time.sleep(0.5)
|
||||
led.value(0)
|
||||
|
||||
# Load in REPL with:
|
||||
# >>> from helpers import *
|
||||
# >>> info()
|
||||
```
|
||||
|
||||
### Auto-run at REPL start
|
||||
|
||||
Create `boot.py` to run code on startup:
|
||||
|
||||
```python
|
||||
# boot.py - Runs on every boot
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
# Optional: Auto-import common modules
|
||||
# from badgeware import screen, display, brushes, shapes
|
||||
# import time
|
||||
|
||||
print("Universe 2025 Badge Ready!")
|
||||
print(f"Free memory: {gc.mem_free()} bytes")
|
||||
```
|
||||
|
||||
## Quick Testing Workflows
|
||||
|
||||
### Test Hardware Function
|
||||
|
||||
```python
|
||||
# Test I2C scan
|
||||
from machine import I2C, Pin
|
||||
i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
|
||||
devices = i2c.scan()
|
||||
print(f"Found devices: {[hex(d) for d in devices]}")
|
||||
|
||||
# Test display
|
||||
from badgeware import screen, display, brushes
|
||||
screen.brush = brushes.color(0, 0, 0)
|
||||
screen.clear()
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.text("REPL Test", 10, 10, 2)
|
||||
display.update()
|
||||
```
|
||||
|
||||
### Test Network Connection
|
||||
|
||||
```python
|
||||
import network
|
||||
import time
|
||||
|
||||
wlan = network.WLAN(network.STA_IF)
|
||||
wlan.active(True)
|
||||
print("Connecting to WiFi...")
|
||||
wlan.connect('YOUR_SSID', 'YOUR_PASSWORD')
|
||||
|
||||
timeout = 10
|
||||
while not wlan.isconnected() and timeout > 0:
|
||||
print(".", end="")
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
|
||||
if wlan.isconnected():
|
||||
print("\nConnected!")
|
||||
print(f"IP: {wlan.ifconfig()[0]}")
|
||||
else:
|
||||
print("\nConnection failed")
|
||||
```
|
||||
|
||||
### Test API Call
|
||||
|
||||
```python
|
||||
import urequests
|
||||
import json
|
||||
|
||||
response = urequests.get('https://api.github.com/zen')
|
||||
print(response.text)
|
||||
response.close()
|
||||
|
||||
# JSON API
|
||||
response = urequests.get('https://api.example.com/data')
|
||||
data = response.json()
|
||||
print(data)
|
||||
response.close()
|
||||
```
|
||||
|
||||
## File System Operations
|
||||
|
||||
### List and Navigate Files
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
# Current directory
|
||||
os.getcwd()
|
||||
|
||||
# List files
|
||||
os.listdir()
|
||||
os.listdir('/lib')
|
||||
|
||||
# File info
|
||||
os.stat('main.py') # Returns (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime)
|
||||
|
||||
# Check if file exists
|
||||
try:
|
||||
os.stat('main.py')
|
||||
print("File exists")
|
||||
except:
|
||||
print("File not found")
|
||||
```
|
||||
|
||||
### Read/Write Files
|
||||
|
||||
```python
|
||||
# Write file
|
||||
with open('test.txt', 'w') as f:
|
||||
f.write('Hello from REPL!\n')
|
||||
|
||||
# Read file
|
||||
with open('test.txt', 'r') as f:
|
||||
content = f.read()
|
||||
print(content)
|
||||
|
||||
# Append to file
|
||||
with open('log.txt', 'a') as f:
|
||||
f.write(f'Log entry: {time.time()}\n')
|
||||
```
|
||||
|
||||
### Remove Files
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
# Remove file
|
||||
os.remove('test.txt')
|
||||
|
||||
# Remove directory (must be empty)
|
||||
os.rmdir('mydir')
|
||||
```
|
||||
|
||||
## Checking Installed Packages
|
||||
|
||||
### Verify Package Installation
|
||||
|
||||
```python
|
||||
# Method 1: Try to import
|
||||
try:
|
||||
import urequests
|
||||
print("urequests is installed")
|
||||
print(f"Location: {urequests.__file__}")
|
||||
except ImportError:
|
||||
print("urequests not installed")
|
||||
|
||||
# Method 2: Check file system
|
||||
import os
|
||||
lib_files = os.listdir('/lib')
|
||||
print("Installed in /lib:")
|
||||
for f in lib_files:
|
||||
print(f" {f}")
|
||||
|
||||
# Method 3: Check sys.path
|
||||
import sys
|
||||
print("Module search paths:")
|
||||
for path in sys.path:
|
||||
print(f" {path}")
|
||||
```
|
||||
|
||||
### Package Version Info
|
||||
|
||||
```python
|
||||
# Some packages have __version__
|
||||
import urequests
|
||||
if hasattr(urequests, '__version__'):
|
||||
print(f"urequests version: {urequests.__version__}")
|
||||
|
||||
# Check MicroPython version
|
||||
import sys
|
||||
print(sys.version)
|
||||
print(sys.implementation)
|
||||
```
|
||||
|
||||
## REPL Tips and Tricks
|
||||
|
||||
### History Navigation
|
||||
|
||||
- **Up/Down arrows**: Navigate command history
|
||||
- **Tab**: Auto-completion (limited support)
|
||||
|
||||
### Copy Output from REPL
|
||||
|
||||
```python
|
||||
# Run command and capture output
|
||||
>>> result = []
|
||||
>>> for i in range(10):
|
||||
... result.append(i * 2)
|
||||
>>> print(result)
|
||||
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
|
||||
|
||||
# Copy from terminal window
|
||||
```
|
||||
|
||||
### Run Script from REPL
|
||||
|
||||
```python
|
||||
# Import and run
|
||||
import myapp
|
||||
myapp.main()
|
||||
|
||||
# Or reload after changes
|
||||
import sys
|
||||
if 'myapp' in sys.modules:
|
||||
del sys.modules['myapp']
|
||||
import myapp
|
||||
```
|
||||
|
||||
### Timing Code
|
||||
|
||||
```python
|
||||
import time
|
||||
|
||||
start = time.ticks_ms()
|
||||
# ... your code here
|
||||
for i in range(1000):
|
||||
x = i * 2
|
||||
end = time.ticks_ms()
|
||||
|
||||
print(f"Execution time: {time.ticks_diff(end, start)}ms")
|
||||
```
|
||||
|
||||
## Common REPL Issues
|
||||
|
||||
**REPL not responding**: Press Ctrl+C to interrupt, or Ctrl+D to soft reset
|
||||
|
||||
**Can't import module**: Check `sys.path`, verify file is in `/lib` or root directory
|
||||
|
||||
**Out of memory**: Run `gc.collect()`, reduce variable usage, delete large objects
|
||||
|
||||
**Module changes not reflected**: Delete from `sys.modules` and re-import
|
||||
|
||||
**Connection lost**: Reconnect with screen or mpremote, check USB cable
|
||||
|
||||
## Using mpremote for Quick Operations
|
||||
|
||||
```bash
|
||||
# Execute Python code remotely
|
||||
mpremote exec "import machine; print(machine.freq())"
|
||||
|
||||
# Run local script on device
|
||||
mpremote run test.py
|
||||
|
||||
# Copy file to device
|
||||
mpremote cp test.py :main.py
|
||||
|
||||
# Copy file from device
|
||||
mpremote cp :main.py local.py
|
||||
|
||||
# Mount local directory on device
|
||||
mpremote mount .
|
||||
|
||||
# Filesystem operations
|
||||
mpremote ls
|
||||
mpremote mkdir /data
|
||||
mpremote rm test.txt
|
||||
|
||||
# Chain commands
|
||||
mpremote connect /dev/tty.usbmodem* cp main.py :main.py exec "import main"
|
||||
```
|
||||
|
||||
## REPL Best Practices
|
||||
|
||||
1. **Save work frequently** - REPL state is lost on reboot
|
||||
2. **Use paste mode** for multi-line code
|
||||
3. **Run `gc.collect()`** before memory-intensive operations
|
||||
4. **Test incrementally** - Build up complex code piece by piece
|
||||
5. **Create helper functions** in a dedicated module
|
||||
6. **Use `print()` liberally** for debugging
|
||||
7. **Soft reset (Ctrl+D)** to clear state between tests
|
||||
8. **Keep REPL sessions short** - Move working code to files
|
||||
|
||||
## Integration with Development Workflow
|
||||
|
||||
1. **Prototype in REPL** - Test ideas interactively
|
||||
2. **Save to file** - Once code works, save to .py file
|
||||
3. **Test from file** - Import and run from REPL
|
||||
4. **Iterate** - Make changes, reload, test
|
||||
5. **Deploy** - Upload final version to badge
|
||||
|
||||
The REPL is your best friend for rapid experimentation and debugging on the Badger 2350!
|
||||
965
skills/python-setup/SKILL.md
Normal file
965
skills/python-setup/SKILL.md
Normal file
@@ -0,0 +1,965 @@
|
||||
---
|
||||
name: python-setup
|
||||
description: Python environment setup on your computer for Badger 2350 development. Use when installing Python, setting up virtual environments, installing development tools like mpremote or ampy, or configuring the computer-side development environment for Badger 2350 projects.
|
||||
---
|
||||
|
||||
# Python Development Environment Setup
|
||||
|
||||
Complete guide to setting up Python on your computer for Universe 2025 (Tufty) Badge development, including virtual environments and all necessary tools.
|
||||
|
||||
## Quick Start (First Time Setup)
|
||||
|
||||
If you're brand new and just want to get started quickly:
|
||||
|
||||
```bash
|
||||
# 1. Check if Python is installed
|
||||
python3 --version
|
||||
# If not installed, see "Install Python" section below
|
||||
|
||||
# 2. Create project directory
|
||||
mkdir ~/badge-projects
|
||||
cd ~/badge-projects
|
||||
|
||||
# 3. Create virtual environment
|
||||
python3 -m venv venv
|
||||
|
||||
# 4. Activate it
|
||||
source venv/bin/activate # macOS/Linux
|
||||
# venv\Scripts\Activate.ps1 # Windows
|
||||
|
||||
# 5. Install badge tools
|
||||
pip install mpremote
|
||||
|
||||
# 6. Test badge connection
|
||||
mpremote exec "print('Badge connected!')"
|
||||
# Should print: Badge connected!
|
||||
|
||||
# ✓ You're ready! Continue to badger-quickstart skill
|
||||
```
|
||||
|
||||
If any command fails, continue with the detailed instructions below.
|
||||
|
||||
## Prerequisites Check
|
||||
|
||||
Before starting detailed setup, check what you already have:
|
||||
|
||||
```bash
|
||||
# Check Python version
|
||||
python3 --version
|
||||
|
||||
# Check pip
|
||||
pip3 --version
|
||||
|
||||
# Check if tools are installed
|
||||
which mpremote
|
||||
which ampy
|
||||
which rshell
|
||||
```
|
||||
|
||||
## Install Python
|
||||
|
||||
### macOS
|
||||
|
||||
**Option 1: Using Homebrew (Recommended)**
|
||||
|
||||
```bash
|
||||
# Install Homebrew if not already installed
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
|
||||
# Install Python
|
||||
brew install python3
|
||||
|
||||
# Verify installation
|
||||
python3 --version
|
||||
pip3 --version
|
||||
```
|
||||
|
||||
**Option 2: Using python.org installer**
|
||||
|
||||
1. Download from https://www.python.org/downloads/
|
||||
2. Run installer
|
||||
3. Check "Add Python to PATH"
|
||||
4. Complete installation
|
||||
|
||||
### Linux (Ubuntu/Debian)
|
||||
|
||||
```bash
|
||||
# Update package list
|
||||
sudo apt update
|
||||
|
||||
# Install Python 3 and pip
|
||||
sudo apt install python3 python3-pip python3-venv
|
||||
|
||||
# Verify installation
|
||||
python3 --version
|
||||
pip3 --version
|
||||
```
|
||||
|
||||
### Linux (Fedora/RHEL)
|
||||
|
||||
```bash
|
||||
# Install Python 3
|
||||
sudo dnf install python3 python3-pip
|
||||
|
||||
# Verify installation
|
||||
python3 --version
|
||||
pip3 --version
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
**Option 1: Using winget (Windows 10/11)**
|
||||
|
||||
```powershell
|
||||
# Install Python
|
||||
winget install Python.Python.3.11
|
||||
|
||||
# Restart terminal, then verify
|
||||
python --version
|
||||
pip --version
|
||||
```
|
||||
|
||||
**Option 2: Using python.org installer**
|
||||
|
||||
1. Download from https://www.python.org/downloads/
|
||||
2. Run installer
|
||||
3. **IMPORTANT**: Check "Add Python to PATH"
|
||||
4. Check "Install pip"
|
||||
5. Complete installation
|
||||
6. Restart terminal
|
||||
|
||||
**Option 3: Using Microsoft Store**
|
||||
|
||||
1. Open Microsoft Store
|
||||
2. Search for "Python 3.11"
|
||||
3. Install
|
||||
4. Verify in terminal
|
||||
|
||||
## Create Project Directory
|
||||
|
||||
Set up a dedicated directory for Badger 2350 projects:
|
||||
|
||||
```bash
|
||||
# Create project directory
|
||||
mkdir -p ~/badger-projects
|
||||
cd ~/badger-projects
|
||||
|
||||
# Create your first project
|
||||
mkdir my-badge-app
|
||||
cd my-badge-app
|
||||
```
|
||||
|
||||
## Set Up Virtual Environment
|
||||
|
||||
Virtual environments isolate project dependencies and prevent conflicts.
|
||||
|
||||
### Create Virtual Environment
|
||||
|
||||
```bash
|
||||
# Create venv in project directory
|
||||
python3 -m venv venv
|
||||
|
||||
# Alternative name
|
||||
python3 -m venv .venv
|
||||
```
|
||||
|
||||
### Activate Virtual Environment
|
||||
|
||||
**macOS/Linux:**
|
||||
|
||||
```bash
|
||||
# Activate
|
||||
source venv/bin/activate
|
||||
|
||||
# Your prompt should change to show (venv)
|
||||
(venv) user@computer:~/badger-projects/my-badge-app$
|
||||
|
||||
# Deactivate when done
|
||||
deactivate
|
||||
```
|
||||
|
||||
**Windows (PowerShell):**
|
||||
|
||||
```powershell
|
||||
# Enable script execution (first time only)
|
||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
# Activate
|
||||
venv\Scripts\Activate.ps1
|
||||
|
||||
# Deactivate when done
|
||||
deactivate
|
||||
```
|
||||
|
||||
**Windows (Command Prompt):**
|
||||
|
||||
```cmd
|
||||
# Activate
|
||||
venv\Scripts\activate.bat
|
||||
|
||||
# Deactivate when done
|
||||
deactivate
|
||||
```
|
||||
|
||||
### Verify Virtual Environment
|
||||
|
||||
```bash
|
||||
# Should show venv Python, not system Python
|
||||
which python3 # macOS/Linux
|
||||
where python # Windows
|
||||
|
||||
# Should be venv location like:
|
||||
# ~/badger-projects/my-badge-app/venv/bin/python3
|
||||
```
|
||||
|
||||
## Install Badger Development Tools
|
||||
|
||||
With virtual environment activated:
|
||||
|
||||
### Core Tools
|
||||
|
||||
```bash
|
||||
# Install mpremote (recommended primary tool)
|
||||
pip install mpremote
|
||||
|
||||
# Install ampy (alternative file management)
|
||||
pip install adafruit-ampy
|
||||
|
||||
# Install rshell (interactive shell)
|
||||
pip install rshell
|
||||
|
||||
# Install esptool (firmware flashing)
|
||||
pip install esptool
|
||||
|
||||
# Verify installations
|
||||
mpremote --version
|
||||
ampy --version
|
||||
rshell --version
|
||||
esptool.py version
|
||||
```
|
||||
|
||||
### Optional Development Tools
|
||||
|
||||
```bash
|
||||
# Thonny IDE (beginner-friendly)
|
||||
pip install thonny
|
||||
|
||||
# Code quality tools
|
||||
pip install black # Code formatter
|
||||
pip install pylint # Linter
|
||||
pip install mypy # Type checker
|
||||
|
||||
# Testing tools
|
||||
pip install pytest # Testing framework
|
||||
pip install pytest-cov # Coverage reporting
|
||||
|
||||
# Documentation tools
|
||||
pip install mkdocs # Documentation generator
|
||||
pip install sphinx # Alternative documentation
|
||||
```
|
||||
|
||||
### Save Dependencies
|
||||
|
||||
Create `requirements.txt` to track dependencies:
|
||||
|
||||
```bash
|
||||
# Generate requirements.txt
|
||||
pip freeze > requirements.txt
|
||||
```
|
||||
|
||||
**Example requirements.txt:**
|
||||
|
||||
```
|
||||
mpremote==1.20.0
|
||||
adafruit-ampy==1.1.0
|
||||
rshell==0.0.32
|
||||
esptool==4.6.2
|
||||
black==23.12.1
|
||||
pylint==3.0.3
|
||||
pytest==7.4.3
|
||||
```
|
||||
|
||||
### Install from requirements.txt
|
||||
|
||||
```bash
|
||||
# Install all dependencies at once
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Or upgrade existing
|
||||
pip install -r requirements.txt --upgrade
|
||||
```
|
||||
|
||||
## Configure Tools
|
||||
|
||||
### mpremote Configuration
|
||||
|
||||
Create alias for easier use:
|
||||
|
||||
**macOS/Linux (.bashrc or .zshrc):**
|
||||
|
||||
```bash
|
||||
# Add to ~/.bashrc or ~/.zshrc
|
||||
alias badge='mpremote connect /dev/tty.usbmodem*'
|
||||
|
||||
# Reload shell
|
||||
source ~/.bashrc # or source ~/.zshrc
|
||||
|
||||
# Usage
|
||||
badge ls
|
||||
badge cp main.py :main.py
|
||||
```
|
||||
|
||||
**Windows (PowerShell profile):**
|
||||
|
||||
```powershell
|
||||
# Open profile
|
||||
notepad $PROFILE
|
||||
|
||||
# Add alias
|
||||
function badge { mpremote connect COM3 @args }
|
||||
|
||||
# Reload
|
||||
. $PROFILE
|
||||
|
||||
# Usage
|
||||
badge ls
|
||||
```
|
||||
|
||||
### ampy Configuration
|
||||
|
||||
Set default port to avoid typing it each time:
|
||||
|
||||
**macOS/Linux:**
|
||||
|
||||
```bash
|
||||
# Add to ~/.bashrc or ~/.zshrc
|
||||
export AMPY_PORT=/dev/tty.usbmodem*
|
||||
|
||||
# Reload
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
|
||||
```powershell
|
||||
# Add to PowerShell profile
|
||||
$env:AMPY_PORT = "COM3"
|
||||
|
||||
# Or set permanently
|
||||
[Environment]::SetEnvironmentVariable("AMPY_PORT", "COM3", "User")
|
||||
```
|
||||
|
||||
## Verify Complete Setup
|
||||
|
||||
Run this verification script:
|
||||
|
||||
```bash
|
||||
# verify_setup.sh (macOS/Linux)
|
||||
#!/bin/bash
|
||||
|
||||
echo "Verifying Badger 2350 Development Setup"
|
||||
echo "========================================"
|
||||
|
||||
# Check Python
|
||||
if command -v python3 &> /dev/null; then
|
||||
echo "✓ Python: $(python3 --version)"
|
||||
else
|
||||
echo "✗ Python not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check pip
|
||||
if command -v pip3 &> /dev/null; then
|
||||
echo "✓ pip: $(pip3 --version)"
|
||||
else
|
||||
echo "✗ pip not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check virtual environment
|
||||
if [[ "$VIRTUAL_ENV" != "" ]]; then
|
||||
echo "✓ Virtual environment: active"
|
||||
else
|
||||
echo "⚠ Virtual environment: not active"
|
||||
fi
|
||||
|
||||
# Check tools
|
||||
tools=(mpremote ampy rshell esptool.py)
|
||||
for tool in "${tools[@]}"; do
|
||||
if command -v $tool &> /dev/null; then
|
||||
echo "✓ $tool: installed"
|
||||
else
|
||||
echo "✗ $tool: not installed"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "========================================"
|
||||
echo "Setup verification complete!"
|
||||
```
|
||||
|
||||
Make executable and run:
|
||||
|
||||
```bash
|
||||
chmod +x verify_setup.sh
|
||||
./verify_setup.sh
|
||||
```
|
||||
|
||||
**Windows PowerShell version:**
|
||||
|
||||
```powershell
|
||||
# verify_setup.ps1
|
||||
Write-Host "Verifying Badger 2350 Development Setup"
|
||||
Write-Host "========================================"
|
||||
|
||||
# Check Python
|
||||
if (Get-Command python -ErrorAction SilentlyContinue) {
|
||||
$version = python --version
|
||||
Write-Host "✓ Python: $version"
|
||||
} else {
|
||||
Write-Host "✗ Python not found"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check pip
|
||||
if (Get-Command pip -ErrorAction SilentlyContinue) {
|
||||
Write-Host "✓ pip: installed"
|
||||
} else {
|
||||
Write-Host "✗ pip not found"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check virtual environment
|
||||
if ($env:VIRTUAL_ENV) {
|
||||
Write-Host "✓ Virtual environment: active"
|
||||
} else {
|
||||
Write-Host "⚠ Virtual environment: not active"
|
||||
}
|
||||
|
||||
# Check tools
|
||||
$tools = @("mpremote", "ampy", "rshell", "esptool.py")
|
||||
foreach ($tool in $tools) {
|
||||
if (Get-Command $tool -ErrorAction SilentlyContinue) {
|
||||
Write-Host "✓ $tool: installed"
|
||||
} else {
|
||||
Write-Host "✗ $tool: not installed"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "========================================"
|
||||
Write-Host "Setup verification complete!"
|
||||
```
|
||||
|
||||
## Test Badge Connection
|
||||
|
||||
Once tools are installed, test connection to badge:
|
||||
|
||||
```bash
|
||||
# List serial ports (macOS/Linux)
|
||||
ls /dev/tty.usb*
|
||||
|
||||
# List serial ports (Windows PowerShell)
|
||||
[System.IO.Ports.SerialPort]::getportnames()
|
||||
|
||||
# Test connection with mpremote
|
||||
mpremote connect /dev/tty.usbmodem* exec "print('Hello from Badger!')"
|
||||
|
||||
# Or on Windows
|
||||
mpremote connect COM3 exec "print('Hello from Badger!')"
|
||||
|
||||
# If successful, you should see: Hello from Badger!
|
||||
```
|
||||
|
||||
## Project Template
|
||||
|
||||
Create a standard project structure:
|
||||
|
||||
```bash
|
||||
# Create structure
|
||||
mkdir -p my-badge-app/{lib,assets,data,tests}
|
||||
cd my-badge-app
|
||||
|
||||
# Create files
|
||||
touch main.py config.py README.md requirements.txt
|
||||
touch lib/__init__.py
|
||||
touch tests/test_main.py
|
||||
|
||||
# Create .gitignore
|
||||
cat > .gitignore <<EOF
|
||||
# Virtual environment
|
||||
venv/
|
||||
.venv/
|
||||
env/
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Local config
|
||||
config.local.py
|
||||
.env
|
||||
EOF
|
||||
|
||||
# Initialize git
|
||||
git init
|
||||
```
|
||||
|
||||
**Project structure:**
|
||||
|
||||
```
|
||||
my-badge-app/
|
||||
├── venv/ # Virtual environment (gitignored)
|
||||
├── main.py # Main application
|
||||
├── config.py # Configuration
|
||||
├── requirements.txt # Python dependencies
|
||||
├── README.md
|
||||
├── .gitignore
|
||||
├── lib/ # Reusable modules
|
||||
│ └── __init__.py
|
||||
├── assets/ # Images, fonts, etc.
|
||||
├── data/ # Runtime data
|
||||
└── tests/ # Test files
|
||||
└── test_main.py
|
||||
```
|
||||
|
||||
## IDE Setup
|
||||
|
||||
### VS Code (Recommended)
|
||||
|
||||
```bash
|
||||
# Install VS Code
|
||||
# macOS
|
||||
brew install --cask visual-studio-code
|
||||
|
||||
# Linux
|
||||
sudo snap install code --classic
|
||||
|
||||
# Windows
|
||||
winget install Microsoft.VisualStudioCode
|
||||
```
|
||||
|
||||
**Recommended Extensions:**
|
||||
|
||||
1. Python (Microsoft)
|
||||
2. Pylance
|
||||
3. Python Debugger
|
||||
4. MicroPython (for syntax)
|
||||
5. GitLens
|
||||
|
||||
**VS Code Settings (.vscode/settings.json):**
|
||||
|
||||
```json
|
||||
{
|
||||
"python.defaultInterpreterPath": "${workspaceFolder}/venv/bin/python",
|
||||
"python.formatting.provider": "black",
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": true,
|
||||
"editor.formatOnSave": true,
|
||||
"files.exclude": {
|
||||
"**/__pycache__": true,
|
||||
"**/*.pyc": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PyCharm
|
||||
|
||||
1. Download from https://www.jetbrains.com/pycharm/
|
||||
2. Open project directory
|
||||
3. Configure interpreter: Settings → Project → Python Interpreter
|
||||
4. Select existing venv or create new one
|
||||
|
||||
### Thonny (Beginner-Friendly)
|
||||
|
||||
```bash
|
||||
# Install Thonny
|
||||
pip install thonny
|
||||
|
||||
# Or download from https://thonny.org
|
||||
|
||||
# Run
|
||||
thonny
|
||||
```
|
||||
|
||||
**Configure Thonny for Badger:**
|
||||
|
||||
1. Tools → Options → Interpreter
|
||||
2. Select "MicroPython (RP2040)"
|
||||
3. Select correct port
|
||||
4. Click OK
|
||||
|
||||
## Workflow Scripts
|
||||
|
||||
### Activate Script
|
||||
|
||||
Create `activate.sh` in project root:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# activate.sh - Quick project activation
|
||||
|
||||
# Activate virtual environment
|
||||
source venv/bin/activate
|
||||
|
||||
# Set badge port
|
||||
export BADGE_PORT="/dev/tty.usbmodem*"
|
||||
|
||||
# Show status
|
||||
echo "Badge development environment activated!"
|
||||
echo "Python: $(which python3)"
|
||||
echo "Badge port: $BADGE_PORT"
|
||||
|
||||
# Quick commands
|
||||
alias badge='mpremote connect $BADGE_PORT'
|
||||
alias deploy='./deploy.sh'
|
||||
|
||||
echo "Ready to develop!"
|
||||
```
|
||||
|
||||
Usage:
|
||||
|
||||
```bash
|
||||
source activate.sh
|
||||
# Now you're ready to work!
|
||||
```
|
||||
|
||||
### Quick Deploy Script
|
||||
|
||||
Create `deploy.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# deploy.sh - Quick deploy to badge
|
||||
|
||||
if [ -z "$BADGE_PORT" ]; then
|
||||
BADGE_PORT="/dev/tty.usbmodem*"
|
||||
fi
|
||||
|
||||
echo "Deploying to badge..."
|
||||
mpremote connect $BADGE_PORT cp main.py :main.py
|
||||
mpremote connect $BADGE_PORT cp config.py :config.py
|
||||
echo "Deployment complete!"
|
||||
```
|
||||
|
||||
Make executable:
|
||||
|
||||
```bash
|
||||
chmod +x deploy.sh activate.sh
|
||||
```
|
||||
|
||||
## Common Setup Issues
|
||||
|
||||
### "command not found: python"
|
||||
|
||||
**macOS/Linux:**
|
||||
```bash
|
||||
# Use python3 instead of python
|
||||
python3 --version # This should work
|
||||
|
||||
# If python3 also not found, install Python (see Install Python section above)
|
||||
|
||||
# Optional: Create alias for convenience
|
||||
alias python=python3
|
||||
alias pip=pip3
|
||||
# Add to ~/.bashrc or ~/.zshrc to make permanent
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
```powershell
|
||||
# Use python instead of python3
|
||||
python --version
|
||||
|
||||
# If not found, reinstall Python with "Add to PATH" checked
|
||||
```
|
||||
|
||||
**Important**: On macOS/Linux, always use `python3` and `pip3` (not `python` and `pip`).
|
||||
|
||||
### Python not in PATH
|
||||
|
||||
**macOS/Linux:**
|
||||
```bash
|
||||
# Add to PATH in ~/.bashrc or ~/.zshrc
|
||||
export PATH="/usr/local/bin:$PATH"
|
||||
export PATH="/opt/homebrew/bin:$PATH" # For M1/M2 Macs
|
||||
|
||||
# Reload shell
|
||||
source ~/.bashrc # or source ~/.zshrc
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
- Reinstall Python with "Add to PATH" checked
|
||||
- Or manually add to PATH:
|
||||
- System Properties → Environment Variables
|
||||
- Add `C:\Python311` and `C:\Python311\Scripts`
|
||||
|
||||
### pip install fails with permissions error
|
||||
|
||||
```bash
|
||||
# Don't use sudo! Use virtual environment instead
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install mpremote
|
||||
```
|
||||
|
||||
### Virtual environment activation fails (Windows)
|
||||
|
||||
```powershell
|
||||
# Enable scripts (run as Administrator)
|
||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
# Then activate normally
|
||||
venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
### Badge not detected
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
# Check if driver needed (usually automatic)
|
||||
ls /dev/tty.usb*
|
||||
|
||||
# Grant permissions
|
||||
sudo chmod 666 /dev/tty.usbmodem*
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```bash
|
||||
# Add user to dialout group
|
||||
sudo usermod -a -G dialout $USER
|
||||
|
||||
# Logout and login for changes to take effect
|
||||
|
||||
# Or use sudo temporarily
|
||||
sudo mpremote connect /dev/ttyACM0
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
- Install USB driver if needed
|
||||
- Check Device Manager for COM port
|
||||
- Try different USB ports
|
||||
|
||||
### mpremote connection fails
|
||||
|
||||
```bash
|
||||
# Try explicit port
|
||||
mpremote connect /dev/tty.usbmodem14201
|
||||
|
||||
# List available ports
|
||||
mpremote connect list
|
||||
|
||||
# Auto-detect port (usually works)
|
||||
mpremote exec "print('Hello')"
|
||||
|
||||
# Check if badge is in bootloader mode
|
||||
# (Hold BOOTSEL button while connecting)
|
||||
```
|
||||
|
||||
### "can't open file" or "No such file" errors
|
||||
|
||||
If you see errors like `can't open file 'test_connection.py'`:
|
||||
|
||||
```bash
|
||||
# This means the script doesn't exist yet
|
||||
|
||||
# Option 1: Use basic verification instead
|
||||
mpremote exec "print('Badge connected!')"
|
||||
|
||||
# Option 2: Create the missing script (see badger-diagnostics skill)
|
||||
|
||||
# Option 3: Use direct mpremote commands instead of scripts
|
||||
mpremote run my_app/__init__.py # Instead of make commands
|
||||
```
|
||||
|
||||
**Common missing files on first setup**:
|
||||
- `test_connection.py` - Use `mpremote exec` for basic testing instead
|
||||
- `deploy.sh` - Use direct `mpremote cp` commands instead
|
||||
- Apps/examples - Create them as you go
|
||||
|
||||
**Don't let missing scripts block you** - use the direct mpremote commands shown in CLAUDE.md and skills.
|
||||
|
||||
## Update Tools
|
||||
|
||||
Keep tools updated:
|
||||
|
||||
```bash
|
||||
# Activate virtual environment first
|
||||
source venv/bin/activate
|
||||
|
||||
# Update pip itself
|
||||
pip install --upgrade pip
|
||||
|
||||
# Update all packages
|
||||
pip install --upgrade mpremote ampy rshell esptool
|
||||
|
||||
# Or update from requirements.txt
|
||||
pip install -r requirements.txt --upgrade
|
||||
```
|
||||
|
||||
## ⚠️ Verify Your Setup
|
||||
|
||||
**CRITICAL**: Always verify your setup is working correctly before starting development.
|
||||
|
||||
### Complete Verification Checklist
|
||||
|
||||
Run through this checklist every time you start a new session:
|
||||
|
||||
```bash
|
||||
# 1. Verify Python installation
|
||||
python3 --version
|
||||
# Should show: Python 3.8.0 or higher
|
||||
|
||||
# 2. Verify virtual environment is activated
|
||||
which python3
|
||||
# Should show path to venv/bin/python3 (not system Python)
|
||||
|
||||
# 3. Verify tools are installed
|
||||
mpremote --version
|
||||
ampy --version
|
||||
# Both should show version numbers
|
||||
|
||||
# 4. Verify badge is connected
|
||||
ls /dev/tty.usb* # macOS/Linux
|
||||
# OR
|
||||
# [System.IO.Ports.SerialPort]::getportnames() # Windows
|
||||
|
||||
# 5. Test badge connection
|
||||
mpremote connect /dev/tty.usbmodem* exec "print('Hello from Badge!')"
|
||||
# Should print: Hello from Badge!
|
||||
|
||||
# 6. Verify badgeware module
|
||||
mpremote connect /dev/tty.usbmodem* exec "import badgeware; print('badgeware OK')"
|
||||
# Should print: badgeware OK
|
||||
```
|
||||
|
||||
### Automated Verification Script
|
||||
|
||||
Create `verify_setup.sh` in your project:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# verify_setup.sh - Complete environment verification
|
||||
|
||||
echo "=========================================="
|
||||
echo "Badger 2350 Environment Verification"
|
||||
echo "=========================================="
|
||||
|
||||
errors=0
|
||||
|
||||
# Check Python
|
||||
if command -v python3 &> /dev/null; then
|
||||
version=$(python3 --version)
|
||||
echo "✓ Python: $version"
|
||||
else
|
||||
echo "✗ Python not found"
|
||||
((errors++))
|
||||
fi
|
||||
|
||||
# Check virtual environment
|
||||
if [[ "$VIRTUAL_ENV" != "" ]]; then
|
||||
echo "✓ Virtual environment: active ($VIRTUAL_ENV)"
|
||||
else
|
||||
echo "⚠ Virtual environment: not active"
|
||||
echo " Run: source venv/bin/activate"
|
||||
((errors++))
|
||||
fi
|
||||
|
||||
# Check mpremote
|
||||
if command -v mpremote &> /dev/null; then
|
||||
echo "✓ mpremote: installed"
|
||||
else
|
||||
echo "✗ mpremote: not installed"
|
||||
echo " Run: pip install mpremote"
|
||||
((errors++))
|
||||
fi
|
||||
|
||||
# Check badge connection
|
||||
if mpremote connect list 2>&1 | grep -q "usb"; then
|
||||
echo "✓ Badge: detected"
|
||||
|
||||
# Test REPL
|
||||
if mpremote exec "print('OK')" 2>&1 | grep -q "OK"; then
|
||||
echo "✓ Badge REPL: working"
|
||||
else
|
||||
echo "✗ Badge REPL: not responding"
|
||||
((errors++))
|
||||
fi
|
||||
|
||||
# Test badgeware module
|
||||
if mpremote exec "import badgeware" 2>&1; then
|
||||
echo "✓ badgeware module: available"
|
||||
else
|
||||
echo "✗ badgeware module: not found"
|
||||
((errors++))
|
||||
fi
|
||||
else
|
||||
echo "✗ Badge: not detected"
|
||||
echo " Check USB connection"
|
||||
((errors++))
|
||||
fi
|
||||
|
||||
echo "=========================================="
|
||||
if [ $errors -eq 0 ]; then
|
||||
echo "✓ ALL CHECKS PASSED - Ready for development!"
|
||||
exit 0
|
||||
else
|
||||
echo "✗ $errors ERROR(S) FOUND - Fix issues before proceeding"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
Make executable: `chmod +x verify_setup.sh`
|
||||
|
||||
**Run this script before every development session**: `./verify_setup.sh`
|
||||
|
||||
### What to Do If Verification Fails
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Python not found | Reinstall Python, check PATH |
|
||||
| venv not active | Run `source venv/bin/activate` |
|
||||
| Tools not installed | Run `pip install -r requirements.txt` |
|
||||
| Badge not detected | Check USB cable, try different port |
|
||||
| REPL not responding | Restart badge, check for other programs using port |
|
||||
| badgeware missing | Badge firmware may need reflashing |
|
||||
|
||||
**Never skip verification** - It catches 90% of issues before they become problems.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always verify setup first** - Run verification script at start of session
|
||||
2. **Always use virtual environments** - Isolate project dependencies
|
||||
3. **Keep requirements.txt updated** - `pip freeze > requirements.txt`
|
||||
4. **Use version control (git)** - Track changes
|
||||
5. **Document your setup** - Update README.md
|
||||
6. **Test on clean environment** - Verify requirements.txt is complete
|
||||
7. **Don't commit venv/** - Add to .gitignore
|
||||
8. **Pin versions** - Avoid "works on my machine" issues
|
||||
|
||||
## Next Steps
|
||||
|
||||
After setup is complete:
|
||||
|
||||
1. ✓ Python installed
|
||||
2. ✓ Virtual environment created
|
||||
3. ✓ Tools installed (mpremote, ampy, etc.)
|
||||
4. ✓ Badge connected and detected
|
||||
5. ✓ Project structure created
|
||||
|
||||
Now you're ready to:
|
||||
- Flash firmware to badge (see `badger-2350-dev` skill)
|
||||
- Create your first app (see `badger-app-creator` skill)
|
||||
- Connect sensors (see `badger-hardware` skill)
|
||||
|
||||
Your development environment is ready! 🎉
|
||||
Reference in New Issue
Block a user