Commit Graph
15 Commits
Author SHA1 Message Date
Honey 7354517b22 Journey: label mode badges with Z/B letters, not just color
Plain colored dots weren't self-explanatory. Each leg badge now shows
a "Z" (Zug) or "B" (Bus) in white inside the blue/green circle --
confirmed legible at actual render size via zoomed crop inspection.
2026-07-23 10:08:51 +02:00
Honey 080cb11501 Journey: stacked header, bus/train icons, drop gray text
- Origin/destination now on their own lines instead of one truncated
  "A -> B" line, which cut off the destination entirely on the small
  panel.
- Each leg gets a colored dot (blue = train, green = bus), using the
  panel's exact palette colors so they render solid, not dithered.
  Mode is read from EFA's product.name (confirmed live: "StadtBus"/
  "RegioBus"/"SchnellBus" vs "S-Bahn"/"Regionalzug"/"Zug").
- Detail-line text switched from dithered gray to solid black -- the
  gray was hard to read at the small font size the panel renders at.
2026-07-23 10:00:36 +02:00
Honey 06b90ffe6c Journey: add regional-only filter and service notices
- connection_type setting (all/regional/long_distance) restricts EFA
  trip requests via inclMOT_N params, so ICE/IC/EC can be filtered out
  for commutes that only care about regional/local transit.
- Surface leg.infos[].infoLinks titles (real disruption/outage notices
  from EFA) on each journey row, marked with a red warning icon.
2026-07-23 09:51:52 +02:00
Honey d5757a00df Fix Journey location search: use EFA's own stopfinder, not Open-Meteo
Open-Meteo's geocoder is built for city-level weather lookups -- it knows
"Münster" but has no idea what "Münster Hauptbahnhof" means, so any
specific station name failed to resolve. Reused it from Weather without
checking it actually fit this use case.

The db_info reference project sidesteps this entirely by requiring the
user to pick existing Home Assistant person/zone entities, which already
carry precise coordinates from HA's own location system -- no equivalent
here, so this needed real geocoding.

Switched geocode() to query the same EFA instances' XML_STOPFINDER_REQUEST
endpoint instead -- the same system that answers the trip request, so
location resolution is guaranteed consistent with what trip planning
understands. Verified live: correctly resolves specific stations ("Münster
Hauptbahnhof" -> "Münster (Westf), Hbf", even the "Muenster" ue-spelling
the user actually typed), plain city names (falls back to the main
station), and district/neighborhood names (e.g. "Hiltrup" -> the actual
Hiltrup stop, not just the vague suburb entry) -- all via matchQuality
scoring and the API's own isBest flag. Re-verified the full pipeline
end-to-end through the real Flask route afterward.
2026-07-22 23:40:08 +02:00
Honey c182fb2f19 Add Journey screen: next connections for a fixed daily commute
New screens/journey.py, ported and simplified from github.com/EiS94/db_info
(a Home Assistant integration). Deliberately uses only the regional EFA
(Elektronische Fahrplanauskunft) backends the reference project queries
(VRR prioritized for NRW routes, falling back to Bavaria/BW instances) and
drops its bahn.de-internal-API source entirely -- that's DB's own
infrastructure, exactly the kind of thing DB has been actively blocking
(see the Departures screen). EFA instances are run by regional transit
authorities, not DB, so they aren't part of that clampdown. Also dropped
the reference project's parallel async fetch-and-score-all-sources
machinery for simple sequential fallback, matching this project's fully
synchronous style.

Verified against live VRR data for a real NRW route (Münster -> Dortmund):
real trains, platforms, transfer counts, even a night bus connection.

Caught a real bug during that live test: EFA returns UTC timestamps (Z
suffix), and datetime.fromisoformat() parses that correctly into an
aware datetime, but strftime() on an aware datetime prints its own
stored hour/minute rather than converting to local time first --
everything displayed 2 hours behind (CEST is UTC+2), which looked like
"next" departures had already left. Fixed by converting with
astimezone() before formatting; re-verified live afterward that
departures now correctly show after the query time.

Settings: origin/destination resolved via Open-Meteo's free geocoding
(same pattern as Weather's city field), how many connections to show
(1/2/3/5), and its own configurable update interval.
2026-07-22 22:33:36 +02:00
Honey a9ed1b6fb4 Clarify Departures covers bus/tram/subway too, add clear found/not-found feedback
The API (HAFAS via db-vendo-client) covers all public transport in one
system, not just trains -- verified with a mocked bus-stop response
(Bus 14/Bus 9/Bus 66 mixed with a regional train, all rendering correctly,
no code changes needed since nothing in check()/render() ever filtered by
mode). Fixed the "Train departures" wording in code comments and settings
UI, which was misleadingly train-specific.

Also added an explicit "Found: <station>" confirmation toast on a
successful station lookup, distinct from the generic "Saved." toast --
previously the only feedback was a passive "Currently: X" hint, easy to
miss and not obviously tied to the save you just did.
2026-07-22 21:45:18 +02:00
Honey 8c9b3e1bfa Add Departures screen (train departure board)
New screens/departures.py using the free, unofficial db-vendo-client REST
API (v6.db.transport.rest) -- no key needed, same "no API key" philosophy
as Weather's Open-Meteo integration. Community-run infra though, not
official DB, and it was actually down (503, then timing out entirely) for
the whole time I was building this -- couldn't live-verify against real
data the way Open-Meteo was earlier, so I built the parsing against the
project's documented example response and stress-tested render() with
mocked requests.get() covering on-time/delayed/cancelled/missing-platform/
very-long-direction cases instead. check() fails soft (logs a warning,
returns None) on any request error, matching the "service might be down"
reality of this API -- the screen shows a plain "not available" message
rather than erroring out.

Settings: station name (resolved to a station ID via /locations, same
pattern as Weather's city->coordinates geocoding), how many departures to
show (3/5/8), and its own configurable update interval via the
get_poll_interval() mechanism built for Weather (defaults to 2 min --
departure boards go stale much faster than weather does).

Caught two real layout bugs while stress-testing render() before shipping:
line names ("ICE 1234") and delayed/cancelled time text ("Fällt aus") were
both wider than the columns I'd budgeted for them, with the second one
overlapping the direction text when right-aligned past its column's own
left edge. Fixed by measuring actual worst-case widths at the chosen font
size instead of guessing, and truncating every field to its column as a
defensive backstop regardless.
2026-07-22 21:38:20 +02:00
Honey 889c062212 Make weather update interval configurable, was hardcoded to 30 min
Screen.poll_interval was a fixed class attribute the scheduler read
directly -- added Screen.get_poll_interval(settings) so a screen can make
it settings-driven instead. WeatherScreen overrides it to read a new
"update_minutes" field (default 30, matching the old hardcoded value).
New number input on the Weather settings page.

Caught a real edge case while testing: `raw or 30` treats an explicit 0
as falsy and silently replaces it with 30 instead of clamping to the
1-minute floor. Not reachable via the UI (min="1" on the input) but the
method should still be correct standalone -- fixed with an explicit
None-check instead.
2026-07-22 20:14:46 +02:00
Honey 8798466e5b Redesign weather with colorful hand-drawn icons, bigger forecast
New screens/weather_icons.py: vector icons drawn with PIL primitives using
the panel's actual 6-color palette (black/white/yellow/red/blue/green, from
epd4in0e's own getbuffer() quantization table) -- sun with rays, line-art
clouds, blue rain/snow, yellow lightning bolt, fog bands. Replaces the old
monochrome unicode-glyph approach entirely (also sidesteps font coverage
gaps -- that's what caused the earlier tofu-box bug).

Forecast row given significantly more vertical space per request. Caught
two real bugs while building this:
- Current-conditions block height was guessed independently of the space
  allocated to it, so it overflowed into the forecast row for real content.
  Now derives the space it needs from actual measured text/icon size
  instead of assuming both fit.
- `scale * 0.045 if has_forecast else 0.05` -- conditional-expression
  precedence bug, silently evaluated to a bare 0.05 (not scale * 0.05) in
  the else branch, crashing font loading with size=0 when forecast is
  empty. Only surfaced by testing that specific case.
- Sun icon's rays extended past the nominal icon_size box used for layout
  math, throckwing off vertical centering when no forecast is shown.
2026-07-21 23:42:22 +02:00
Honey a5fd39f2af Make the progress bar purely decorative, drop live position tracking
positionMs changes every poll (~20s), so the old live progress bar/elapsed
time forced a full flash constantly, even mid-song -- this panel has no
partial refresh, so there's no way to show real progress without that.
Now it's an outline + total length only, driven by nothing that changes
while the same track keeps playing. Verified the rendered image is
byte-identical across different positionMs values for the same track.

Also dropped elapsed/elapsed_text/progress_pct from the track dict
entirely now that nothing renders them.
2026-07-21 23:28:27 +02:00
Honey 5baa45904c Fix off-center Navidrome layout
Two separate imbalances:
- Cover art was top-aligned when width-constrained (landscape, cover_size
  capped by width*0.5), leaving dead space below it instead of centering
  in the available height.
- The text column always started drawing at the very top and let the
  source line's bottom-pin create a big empty gap for tracks with
  short/sparse metadata -- looked top-heavy and unbalanced.

Text column is now built as (height, draw_fn) pairs first so the total
height is known before committing to a starting y, then centered in the
space above the reserved source-line area.
2026-07-21 23:25:44 +02:00
Honey a19a7ea49e Fix manual switch reverting instantly, add weather forecast
- scheduler: "Show X now" was a true one-shot -- reverted on the very next
  5s tick, causing the flash-to-X-then-flash-back-to-clock behavior. Now
  pins for 5 minutes (still preemptable by Navidrome priority, which stays
  authoritative). Added a "Resume automatic" action/button and status
  display showing what's pinned and until when.
- weather: daily forecast strip (3/5/7 days, selectable in settings) below
  current conditions, via Open-Meteo's daily= params in the same request.
  Fixed a bad symbol choice along the way -- DejaVu Sans has no glyph for
  the partly-cloudy emoji, was rendering as a tofu box; swapped to the
  plain cloud symbol, verified glyph coverage for the whole WMO code table.
2026-07-21 23:06:16 +02:00
Honey bfe4616f20 Add Picture screen: upload a file or point at a URL
New screen alongside clock/weather/navidrome. Upload goes through the web
UI (validated with PIL before accepting, stored under web/static/uploads/,
gitignored), or point it at a URL re-fetched every 30 minutes. Cover/contain
fit modes (new fit_cover/fit_contain helpers in render_utils.py, generalized
from the square-only cover-art cropping already used by the Navidrome
screen), plus the same contrast-boost/grayscale options that screen has.
2026-07-21 22:56:32 +02:00
Honey 61348be674 Landscape orientation, redesigned web UI, rotation on/off + minutes, weather by city name
- Panel is mounted sideways: render landscape (600x400), rotate to the
  panel's native portrait orientation right before writing to hardware.
- Web UI: card-based layout, dark mode, active nav highlighting, save
  confirmation toasts, error banners.
- Rotation: separate enabled/disabled toggle (off pins to the first screen
  in the list) and the interval is now in minutes instead of raw seconds.
  Migrates existing config.json from the old interval_seconds field.
- Weather: type a city name instead of hunting down coordinates, resolved
  via Open-Meteo's free geocoding API. Manual lat/long still available as
  a fallback.
2026-07-21 22:51:16 +02:00
Honey 66464291e1 Initial commit: Paperwall, a standalone e-ink dashboard
Navidrome now-playing, clock, and weather for a Waveshare epd4in0e panel,
with a local web UI and install/uninstall scripts for the Pi.
2026-07-21 22:29:45 +02:00