feat(3-1): stale state detection and dimmed display

When fetch times out or returns empty after prior data, retain last aircraft
with is_stale=True and render them as black outlines so the display stays
informative rather than blank or crashing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt Edholm
2026-04-22 23:47:04 -04:00
parent 316e7aa9a8
commit 833a7f0917
6 changed files with 131 additions and 28 deletions
+27 -3
View File
@@ -1,8 +1,10 @@
from __future__ import annotations
import dataclasses
import logging
import time
import requests
from PIL import Image, ImageDraw, ImageFont
from planemapper.constants import (
@@ -13,6 +15,7 @@ from planemapper.constants import (
)
from planemapper.display import DisplayInterface, WaveshareDisplay
from planemapper.fetcher import HttpFetcher
from planemapper.models import Aircraft
from planemapper.provisioning.config import read as read_config
from planemapper.renderer.basemap import load as load_basemap
from planemapper.renderer.projection import MapBounds
@@ -34,9 +37,28 @@ def _make_startup_screen() -> Image.Image:
return image
def _run_one_cycle(renderer: Renderer, fetcher: HttpFetcher, display: DisplayInterface) -> None:
def _run_one_cycle(
renderer: Renderer,
fetcher: HttpFetcher,
display: DisplayInterface,
last_aircraft: list[Aircraft],
) -> list[Aircraft]:
t0 = time.monotonic()
aircraft_list = fetcher.fetch()
stale_needed = False
try:
fresh = fetcher.fetch()
except requests.Timeout:
log.warning("fetch timeout — using stale data")
fresh = []
stale_needed = True
else:
stale_needed = len(fresh) == 0 and len(last_aircraft) > 0
if stale_needed:
aircraft_list = [dataclasses.replace(a, is_stale=True) for a in last_aircraft]
else:
aircraft_list = fresh
t1 = time.monotonic()
image = renderer.render(aircraft_list)
t2 = time.monotonic()
@@ -52,6 +74,7 @@ def _run_one_cycle(renderer: Renderer, fetcher: HttpFetcher, display: DisplayInt
)
if total > RENDER_WARN_S:
log.warning("render slow: %.1fs > %ds threshold", total, RENDER_WARN_S)
return aircraft_list
def main() -> None:
@@ -68,6 +91,7 @@ def main() -> None:
display = WaveshareDisplay()
startup = _make_startup_screen()
display.show(startup)
last: list[Aircraft] = []
while True:
_run_one_cycle(renderer, fetcher, display)
last = _run_one_cycle(renderer, fetcher, display, last)
time.sleep(REFRESH_INTERVAL_S)