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.
This commit is contained in:
1 parent
889c062212
commit
8c9b3e1bfa
6 files changed
+287
-2
No files matched your search
+32
@@ -98,6 +98,38 @@ def create_app(config, screens: dict, scheduler) -> Flask:
|
||||
|
||||
return render_template("settings_weather.html", settings=config.get_screen_settings("weather"), error=error)
|
||||
|
||||
@app.route("/settings/departures", methods=["GET", "POST"])
|
||||
def settings_departures():
|
||||
error = None
|
||||
if request.method == "POST":
|
||||
station_query = request.form.get("station", "").strip()
|
||||
results = int(request.form.get("results", 5) or 5)
|
||||
if results not in (3, 5, 8):
|
||||
results = 5
|
||||
update_minutes = max(1, int(request.form.get("update_minutes", 2) or 2))
|
||||
|
||||
if station_query:
|
||||
try:
|
||||
found = screens["departures"].find_station(station_query)
|
||||
except requests.exceptions.RequestException:
|
||||
found = None
|
||||
if found is None:
|
||||
error = f'Could not find a station matching "{station_query}" -- try a different spelling, or the service might be temporarily down.'
|
||||
else:
|
||||
config.update_screen_settings("departures", {
|
||||
"station": station_query,
|
||||
"station_id": found["id"],
|
||||
"station_label": found["name"],
|
||||
"results": results,
|
||||
"update_minutes": update_minutes,
|
||||
})
|
||||
return redirect(url_for("settings_departures", saved=1))
|
||||
else:
|
||||
config.update_screen_settings("departures", {"results": results, "update_minutes": update_minutes})
|
||||
return redirect(url_for("settings_departures", saved=1))
|
||||
|
||||
return render_template("settings_departures.html", settings=config.get_screen_settings("departures"), error=error)
|
||||
|
||||
@app.route("/settings/picture", methods=["GET", "POST"])
|
||||
def settings_picture():
|
||||
error = None
|
||||
|
||||
Reference in new issue
Block a user