Add a headless smoke-test harness for the Presto microPython app using pimoroni-emulator mock modules. A PRESTO_TEST_MODE=1 guard prevents main() from running at import time so draw functions can be called directly from tests. - presto/main.py: wrap main() with PRESTO_TEST_MODE guard - presto/tests/: conftest with emulator fixtures, 7 smoke tests - presto/mise.toml: add [tasks.test] - presto/README.md, presto/AGENTS.md: add Testing section - .gitignore: exclude test __pycache__, pytest_cache, fixture pngs
5.1 KiB
Presto App Guidance
This directory contains MicroPython apps for the Pimoroni Presto. The main app is
main.py, deployed to the device as main.py.
Context To Read First
- Read
README.mdbefore changing setup, deployment, user-facing behavior, or API response assumptions. - Read
main.pybefore changing layout, touch handling, networking, display sleep, or performance-sensitive code. - The root project conventions still apply, but most Phoenix/Elixir conventions are not relevant inside
presto/.
Deployment And Verification
mise run presto # deploy and reset
Manual deployment:
mpremote fs cp main.py :main.py
mpremote fs cp secrets.py :secrets.py
mpremote reset
Syntax check (no __pycache__):
python3 -c "import py_compile; py_compile.compile('main.py', cfile='/tmp/main.pyc', doraise=True)"
Do not claim device behavior is verified unless it was tested on the physical Presto.
Testing
The test harness lives in presto/tests/ and uses the pimoroni-emulator's
mock MicroPython modules for headless smoke testing.
- Run tests:
mise run test - Tests import
main.pywithout starting the event loop (guarded byPRESTO_TEST_MODE=1). - Smoke tests cover every screen state. When adding a new screen, add a
corresponding test in
presto/tests/test_screens.py. - All tests use mock data from
make_mock_record(). No network calls are made during test execution —fetch_thumbnailis monkey-patched to raise on any accidental network access. - Screenshots are saved to
presto/tests/fixtures/for manual visual inspection.
Hardware And Runtime Constraints
- Stock Presto firmware: MicroPython, PicoGraphics, touch driver,
urequests,ntptime, usuallyjpegdec. - Treat as single-threaded. Do not assume
_threador user-accessible multicore. - Keep memory pressure low: no large in-memory buffers, no aggressive response caching, no
gc.collect()in hot render paths. - Network calls (
urequests) are blocking. Never make HTTP requests during drag scrolling or inside repeated redraw loops.
State Management
- Every view state (
STATE_STARTUP,STATE_MONTH,STATE_DAY,STATE_RECORD) must be handled inredraw_current_view()for correct display sleep/wake. - When transitioning into a view, reset its scroll offset to 0 so it always starts at the top.
- When adding a new state, update
main()globals,redraw_current_view(), the main loop dispatch, and any related state cleanup on navigation.
Scroll Performance
These rules apply to any scrollable view (day list, detail page, or future additions).
- The scroll hot path must not: measure text, join strings, sanitize text, compute layout heights, or fetch images over the network.
- Pre-compute and cache all display strings and dimensions after data arrives and before the first draw.
- Throttle drag redraws by both time (
DRAG_REDRAW_MS) and pixel delta (DRAG_REDRAW_PX). Preserve pending delta on touch release and redraw once. - Each scrollable view gets its own offset variable and its own content-height cache. Never reuse one view's scroll state for another.
- If the view has a fixed header, use
display.set_clip()/display.remove_clip()to prevent scrollable content from drawing over it.
Image Handling
- Row thumbnails prefer
micro_cover_url→mini_cover_url→thumb_url. Detail/large cover prefersthumb_url→mini_cover_url→micro_cover_url. - Cache downloaded image bytes on the record dict. Use separate cache keys for different sizes (e.g.,
_thumb_datafor rows,_detail_thumb_datafor detail view). Never fetch images during drag — show placeholders instead and repaint real covers on release. - JPEG is the practical default. PNG has worse memory characteristics; raw/RGB565 needs a confirmed stable blit API on the target firmware.
Text Rendering
- All user-visible text shown with bitmap fonts must pass through
display_text()to replace unsupported punctuation (smart quotes, dashes, ellipsis). Do not strip diacritics — the font can render them. release_dateis displayed as year only (first 4 characters).
Layout
- Use named constants for all pixel geometry. No scattered literals.
- Keep header geometry consistent across views: same height, button dimensions, and side margins.
Display Sleep
- Sleep by setting backlight to
0.0. WiFi stays enabled. - The first touch after sleep must wake the backlight and be consumed — it must not also activate a button or trigger a scroll.
- On wake,
set_today()refreshes the global date from the system clock, WiFi is reconnected if it dropped, and the current view is always redrawn so the today-highlight stays current across midnight.
API Contract
GET https://music-library.claudio-ortolina.org/api/v1/collection/on_this_day?date=YYYY-MM-DD
Authorization: Bearer <API_TOKEN>
Fields used by the app: title, artists, format, release_date, genres, record_type, purchased_at, micro_cover_url, mini_cover_url, thumb_url, selected_release_id.
POST /api/v1/collection/:record_id/scrobble Authorization: Bearer <API_TOKEN>
When adding or changing API assumptions, update README.md.