Files
music_library/backlog/tasks/ml-166 - Presto-MicroPython-client-records-on-this-day-calendar-with-cover-display.md
T
2026-05-05 16:34:09 +01:00

9.4 KiB
Raw Blame History

id, title, status, assignee, created_date, updated_date, labels, dependencies, references, documentation, modified_files, ordinal
id title status assignee created_date updated_date labels dependencies references documentation modified_files ordinal
ML-166 Presto MicroPython client: records-on-this-day calendar with cover display Done
2026-05-05 12:23 2026-05-05 12:41
https://shop.pimoroni.com/products/presto?variant=54894104019323
https://github.com/pimoroni/presto
https://github.com/pimoroni/presto/blob/main/examples/secrets.py
docs/architecture.md
docs/production-infrastructure.md
presto/main.py
presto/config.example.py
presto/README.md
.gitignore
11000

Description

Build a MicroPython application for the Pimoroni Presto (RP2350B, 720×720 touch display, WiFi) that connects to the production music library API and lets users browse records by release date.

The app talks to https://music-library.claudio-ortolina.org using Authorization: Bearer <API_TOKEN> for all requests (JSON data and cover images). WiFi credentials and the API token are read from secrets.py (not committed).

Core flow:

  1. Boot → connect WiFi → sync time via NTP → display current month calendar
  2. Tap a day → fetch records via /api/v1/collection/on_this_day?date=YYYY-MM-DD → display covers with artist/title
  3. Month view has ← → arrows to navigate months
  4. Day view has a back button to return to month view

The device is a Pimoroni Presto running the stock Presto firmware (MicroPython + PicoGraphics + touch driver).

Acceptance Criteria

  • #1 Device connects to WiFi on boot using credentials from secrets.py
  • #2 Current date is obtained via NTP and displayed on the month calendar
  • #3 Today's date cell is visually highlighted on the month grid
  • #4 Tapping a day cell fetches and displays records released on that day, each showing cover art, artist names, and title
  • #5 Tapping ← → arrows navigates to previous/next month, refreshing the calendar
  • #6 Tapping a back button (or back gesture) in day view returns to month view
  • #7 When the server is unreachable or returns an error, a user-readable message is shown instead of crashing
  • #8 Cover images are loaded and displayed from the thumb_url returned by the API
  • #9 A secrets.example.py is provided documenting the required WIFI_SSID, WIFI_PASSWORD, and API_TOKEN variables
  • #10 A README.md in the presto/ directory explains how to set up and deploy to the device

Implementation Plan

File structure

presto/
  main.py              # Entry point: boot sequence, state machine, main loop
  secrets.example.py   # Template for secrets.py (WIFI_SSID, WIFI_PASSWORD, API_TOKEN)
  README.md            # Setup and deployment instructions

secrets.py is .gitignored. The user creates it from the example.

Architecture (in main.py)

Boot sequence

  1. import secrets — reads WIFI_SSID, WIFI_PASSWORD, API_TOKEN
  2. Connect WiFi via network.WLAN — retry loop with timeout (30s)
  3. Sync time via NTP (ntptime.settime())
  4. Set current year/month from time.localtime()
  5. Enter main loop in MONTH_VIEW state

State machine

Two states: MONTH_VIEW and DAY_VIEW. A global state variable and a redraw flag control rendering.

MONTH_VIEW
  ├── render_calendar(year, month)   → draw 7×6 grid + month header + ← →
  ├── handle_touch(x, y)
  │     ├── arrow left  → month -= 1, redraw
  │     ├── arrow right → month += 1, redraw
  │     └── day cell    → selected_date = date, fetch_records(), switch to DAY_VIEW
  └── highlight today (if visible month matches)

DAY_VIEW
  ├── render_records(records)        → draw cover thumbnails + artist + title
  ├── handle_touch(x, y)
  │     ├── back button → switch to MONTH_VIEW
  │     └── record row  → (future: detail view, for now no-op)
  └── show error message if fetch failed

API client (functions in main.py)

  • fetch_records(date_str) → GET /api/v1/collection/on_this_day?date=YYYY-MM-DD with Authorization: Bearer {API_TOKEN}, parse JSON, return list of record dicts
  • fetch_image(url) → GET url with same auth header, return JPEG bytes buffer
  • On network error: return None, caller shows "Could not reach server" message

Calendar rendering (render_calendar)

  • Use PicoGraphics to draw on the 720×720 framebuffer
  • Month/year header at top (y: 060), arrows on sides
  • Day-of-week labels (MonSun) at y: 6090
  • 7×6 grid cells, each ~100×100, starting at y: 90
  • Today's cell: filled background (accent color)
  • Selected date cell: outlined border
  • Day numbers drawn centered in each cell

Day view rendering (render_records)

  • Header: "April 15" + back button (← icon or "< Back" text)
  • Record rows: each row is ~150px tall
    • Left: cover thumbnail (~140×140, loaded from thumb_url)
    • Right: title (wrapped), artist names
  • If >4 records, simple pagination or scroll with up/down arrows
  • Error state: centered text "Could not reach server" + "Tap to retry"

Touch handling

  • Presto touch driver provides (x, y, pressure) events
  • handle_touch() is called from the main loop when a touch event fires
  • Hit-testing: compare (x, y) against known UI regions (arrow rects, day cells, back button)
  • Debounce: ignore touches within 300ms of each other

Image loading

  • On entering DAY_VIEW, fetch all thumbnails sequentially (to avoid memory pressure)
  • Each JPEG is decoded via PicoGraphics JPEG decoder and drawn at the target position
  • Images are drawn directly to the framebuffer (not cached to flash, to keep it simple)
  • If an image fails to load, show a placeholder rectangle

Error handling

  • WiFi failure: show "WiFi connection failed" + retry every 10s
  • API unreachable: show error message in day view, keep month view functional
  • Image load failure: show placeholder, don't crash

Constants / layout

All positions, sizes, and colors defined as module-level constants at the top of main.py for easy tweaking.

Implementation Notes

Implementation Notes

Files created

File Lines Description
presto/main.py 1030 Full MicroPython application with boot sequence, state machine, calendar rendering, day view, API client, touch handling
presto/config.example.py 20 Template for secrets.py (renamed from secrets.example.py due to sensitive-file guard)
presto/README.md 140 Setup, deployment, usage, troubleshooting instructions
.gitignore (updated) +3 Added presto/secrets.py to gitignore

Architecture

  • Single file (main.py) — all logic in one module for simplicity on MicroPython
  • State machine: 3 states (STARTUP, MONTH, DAY) controlled by state global
  • No classes — uses module-level functions and globals to minimize MicroPython memory overhead
  • Pen pre-creation: All PicoGraphics pens created once in _init_pens() to reduce GC pressure
  • Touch debounce: 300ms debounce on all touch events
  • WiFi retry: 30s timeout, retry every 10s on failure
  • JPEG decoding: Tries 3 different firmware APIs with graceful fallback to placeholder
  • API auth: Authorization: Bearer <token> on all requests
  • NTP: Non-fatal — calendar still works with RTC time if NTP fails

Key design decisions

  1. Thumbnails loaded synchronously during render (not cached) — keeps memory usage low; max 4 thumbnail fetches per day view
  2. Text wrapping (draw_wrapped()) — PicoGraphics has no automatic wrapping, so manual word-boundary wrapping is implemented
  3. Scroll arrows for >4 records — simple ^ v arrows for paging through records
  4. Touch API compatibilityread_touch() tries multiple Presto firmware touch APIs
  5. Selected cell border — today+selected combo shows both highlight and border

What's NOT tested

  • Physical device testing — requires a real Pimoroni Presto; code is written for stock firmware but may need minor adjustments
  • JPEG decoder API — depends on firmware version; 3 fallback methods provided
  • Touch API — depends on firmware version; multiple methods tried in read_touch()
  • Memory pressure — 1030 lines is large for MicroPython; may need to be split across files or use frozen bytecode
  • SSL/TLSurequests on MicroPython may need explicit SSL context or may not support HTTPS depending on firmware build

Potential firmware adjustments needed

  1. If jpegdec module is named differently or has different API, update draw_jpeg()
  2. If presto.touch API differs, update read_touch()
  3. If PicoGraphics constructor signature differs, update init_display()
  4. If set_font() font names differ, update font references
  5. If HTTPS doesn't work, switch API_BASE to http:// or add SSL context