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.
This commit is contained in:
Honey committed 2026-07-21 22:51:16 +02:00
1 parent 7c197e20a5
commit 61348be674
11 files changed
+393 -104

No files matched your search

+24 -1
View File
@@ -11,6 +11,7 @@ from screens.render_utils import load_font, text_width
logger = logging.getLogger(__name__)
HTTP_TIMEOUT = 10
GEOCODE_URL = "https://geocoding-api.open-meteo.com/v1/search"
# WMO weather codes -> (German description, symbol). Not exhaustive, covers
# the common cases; unmapped codes fall back to a generic description.
@@ -47,7 +48,29 @@ class WeatherScreen(Screen):
poll_interval = 1800 # 30 minutes -- weather doesn't need to be fresher
def default_settings(self) -> dict:
return {"latitude": None, "longitude": None}
return {"city": "", "latitude": None, "longitude": None}
def geocode(self, city: str):
"""Resolve a city name to coordinates via Open-Meteo's free geocoding
API (no key needed). Returns {"latitude", "longitude", "label"} or
None if nothing matched."""
response = requests.get(
GEOCODE_URL,
params={"name": city, "count": 1, "language": "de"},
timeout=HTTP_TIMEOUT,
)
response.raise_for_status()
results = response.json().get("results") or []
if not results:
return None
top = results[0]
label_parts = [top.get("name"), top.get("admin1"), top.get("country")]
return {
"latitude": top["latitude"],
"longitude": top["longitude"],
"label": ", ".join(p for p in label_parts if p),
}
def check(self, settings: dict):
lat = settings.get("latitude")