Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ The timer utilizes the Waveshare 1.28-inch round touch display, allowing for con

The firmware includes a compact proportional bitmap font rendered directly at the display's native resolution. It replaces enlargement of MicroPython's 8x8 framebuffer font, so large countdown digits and labels retain smooth shapes instead of scaling into square pixels.

The running track and rest countdown uses the 74-pixel native font, the closest available pre-rendered size to a 10% increase from the previous 64-pixel countdown.

`font_data.py` and its flash-backed `font_data*.bin` glyph assets are generated from Montserrat SemiBold. The assets contain pre-rasterized native UI sizes, allowing the Pico to use its fast framebuffer blitter without holding the complete font in RAM. To regenerate them, install Pillow and run:

```sh
Expand Down
13 changes: 8 additions & 5 deletions live_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@


LIVE_LOOP_DELAY_SEC = 0.05
# Native font size 7 is 74 px: the nearest available size to 10% above the
# previous 64 px countdown (target 70.4 px).
COUNTDOWN_TEXT_SIZE = 7


def _visible_times(session, now):
Expand All @@ -21,13 +24,13 @@ def track_live_frame(session, now, lcd):
"""Return all values visible on the track-session screen."""
remaining, elapsed = _visible_times(session, now)
if now >= session.end_time:
return ("00:00", elapsed, 6, lcd.red, lcd.black)
return ("00:00", elapsed, COUNTDOWN_TEXT_SIZE, lcd.red, lcd.black)

if now < session.last_15:
return (remaining, elapsed, 6, None, None)
return (remaining, elapsed, COUNTDOWN_TEXT_SIZE, None, None)
if now < session.last_5:
return (remaining, elapsed, 6, lcd.salmon, lcd.black)
return (remaining, elapsed, 6, lcd.lilac, None)
return (remaining, elapsed, COUNTDOWN_TEXT_SIZE, lcd.salmon, lcd.black)
return (remaining, elapsed, COUNTDOWN_TEXT_SIZE, lcd.lilac, None)


def rest_live_frame(session, now, lcd):
Expand All @@ -38,7 +41,7 @@ def rest_live_frame(session, now, lcd):
return (
remaining,
elapsed,
6,
COUNTDOWN_TEXT_SIZE,
lcd.blue,
None,
)
Expand Down
49 changes: 48 additions & 1 deletion tests/test_live_display.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import math
import unittest

from live_display import rest_live_frame, run_live_display, track_live_frame
from font_renderer import measure_text, pixel_height
from live_display import (
COUNTDOWN_TEXT_SIZE,
rest_live_frame,
run_live_display,
track_live_frame,
)
from timing import SessionTracker


Expand All @@ -26,6 +33,31 @@ class FakeLCD:


class LiveDisplayTests(unittest.TestCase):
def test_countdown_uses_nearest_native_size_to_ten_percent_larger(self):
previous_height = pixel_height(6)
target_height = previous_height * 1.10
selected_height = pixel_height(COUNTDOWN_TEXT_SIZE)

self.assertEqual(7, COUNTDOWN_TEXT_SIZE)
self.assertEqual(74, selected_height)
self.assertLess(
abs(selected_height - target_height),
abs(previous_height - target_height),
)

def test_maximum_countdown_width_fits_the_live_screen_position(self):
display_radius = 120
y_position = 82
text_height = pixel_height(COUNTDOWN_TEXT_SIZE)
text_width = measure_text("60:00", COUNTDOWN_TEXT_SIZE)

for edge_y in (y_position, y_position + text_height - 1):
distance_from_center = edge_y - display_radius
visible_width = 2 * math.sqrt(
(display_radius ** 2) - (distance_from_center ** 2)
)
self.assertLessEqual(text_width, visible_width)

def test_simulated_session_redraws_at_most_once_per_visible_second(self):
clock = FakeClock()
lcd = FakeLCD()
Expand Down Expand Up @@ -111,12 +143,27 @@ def test_track_frames_include_warning_and_overrun_state(self):
last_5 = track_live_frame(session, 670, lcd)
overrun = track_live_frame(session, 700, lcd)

self.assertTrue(
all(
frame[2] == COUNTDOWN_TEXT_SIZE
for frame in (running, last_15, last_5, overrun)
)
)
self.assertEqual((None, None), running[3:])
self.assertEqual((lcd.salmon, lcd.black), last_15[3:])
self.assertEqual((lcd.lilac, None), last_5[3:])
self.assertEqual("00:00", overrun[0])
self.assertEqual((lcd.red, lcd.black), overrun[3:])

def test_rest_frame_uses_larger_countdown_size(self):
lcd = FakeLCD()
session = SessionTracker(duration_mins=10, clock=lambda: 100)
session.start_session()

frame = rest_live_frame(session, 100, lcd)

self.assertEqual(COUNTDOWN_TEXT_SIZE, frame[2])

def test_loop_delay_must_be_bounded(self):
session = SessionTracker(duration_mins=1, live=True)
with self.assertRaises(ValueError):
Expand Down
Loading