From 38c684a8598f9867d945ff8b0f853699f7b08a88 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Thu, 3 Sep 2026 00:52:34 -0400 Subject: [PATCH] fix(ledmatrix-weather): keep the radar timestamp on the panel weather_radar.py resolved the 4x6 face three levels above itself, which is the LEDMatrix root only when the plugins directory sits inside the core tree. From anywhere else the load failed and PIL's default font took over -- taller than the 6px the overlay is laid out for -- so the label at (2, height - 8) was drawn past the bottom edge of the panel. The safety harness passes either way: PIL clips at draw time, so a bounds check on what was requested cannot see what was lost. Rendering the radar screen for this README is what surfaced it -- the bottom row of the panel had 22 lit pixels cut mid-glyph. After the fix the label ends two rows clear of the edge. README: real rendered screenshots of all five display modes and of the current conditions screen at four panel sizes, from the recorded Open-Meteo response the test harness already ships, so they reproduce exactly. Documents api_key, location_latitude, location_longitude and both dynamic_duration keys, taking the plugin to 35 of 35 settings. Corrects the radar_range_miles default, which the table gave as 50 where the schema says 75. check_plugin.py: 36 pass, 4 pre-existing almanac golden drifts that reproduce identically on main without this branch -- filed as #391, goldens untouched. Co-Authored-By: Claude Opus 5 --- README.md | 2 +- docs/assets/ledmatrix-weather/hero.png | Bin 0 -> 2773 bytes docs/assets/ledmatrix-weather/modes.png | Bin 0 -> 22528 bytes docs/assets/ledmatrix-weather/panel-sizes.png | Bin 0 -> 20477 bytes docs/assets/ledmatrix-weather/shots.json | 151 ++++++++++++++++++ plugins.json | 6 +- plugins/ledmatrix-weather/README.md | 28 +++- plugins/ledmatrix-weather/manifest.json | 10 +- plugins/ledmatrix-weather/weather_radar.py | 31 +++- scripts/docs_render_support/sitecustomize.py | 62 +++++++ scripts/render_docs_assets.py | 6 +- 11 files changed, 283 insertions(+), 13 deletions(-) create mode 100644 docs/assets/ledmatrix-weather/hero.png create mode 100644 docs/assets/ledmatrix-weather/modes.png create mode 100644 docs/assets/ledmatrix-weather/panel-sizes.png create mode 100644 docs/assets/ledmatrix-weather/shots.json diff --git a/README.md b/README.md index 9fae8532..e6bc759e 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \ | Plugin | Description | Preview | |--------|-------------|---------| -| [Weather Display](./plugins/ledmatrix-weather/) | Current conditions, hourly & daily forecasts (Open-Meteo) | | +| [Weather Display](./plugins/ledmatrix-weather/) | Current conditions, hourly & daily forecasts (Open-Meteo) | ledmatrix-weather on an LED panel | | [Tide Display](./plugins/tide-display/) | Coastal tides with animated wave level, schedule & 24-hour chart (NOAA) | | ### Media (2) diff --git a/docs/assets/ledmatrix-weather/hero.png b/docs/assets/ledmatrix-weather/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..7d4c9b4ad4ee76c73129f9959c1bded460669704 GIT binary patch literal 2773 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 literal 0 HcmV?d00001 diff --git a/docs/assets/ledmatrix-weather/modes.png b/docs/assets/ledmatrix-weather/modes.png new file mode 100644 index 0000000000000000000000000000000000000000..b068290e19657057b58ca391e6f9f804af785229 GIT binary patch literal 22528 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 PrMaS@b}>!v`tSb_bCom? literal 0 HcmV?d00001 diff --git a/docs/assets/ledmatrix-weather/panel-sizes.png b/docs/assets/ledmatrix-weather/panel-sizes.png new file mode 100644 index 0000000000000000000000000000000000000000..d8b6e32950fbdbc5afffe8413d4e80bd94f09785 GIT binary patch literal 20477 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 NF|(sZKc2bre*o4T=q3OF literal 0 HcmV?d00001 diff --git a/docs/assets/ledmatrix-weather/shots.json b/docs/assets/ledmatrix-weather/shots.json new file mode 100644 index 00000000..9865a5b2 --- /dev/null +++ b/docs/assets/ledmatrix-weather/shots.json @@ -0,0 +1,151 @@ +{ + "plugin": "ledmatrix-weather", + "defaults": { + "width": 128, + "height": 32, + "scale": 6, + "freeze_time": "2026-06-14T17:00:00", + "mock_data": "../../../plugins/ledmatrix-weather/test/fixtures/mock.json", + "config": { + "enabled": true, + "location_city": "Dallas", + "location_state": "Texas", + "location_country": "US", + "units": "imperial", + "show_current_weather": false, + "show_hourly_forecast": false, + "show_daily_forecast": false, + "show_almanac": false, + "show_radar": false + } + }, + "shots": [ + { + "name": "hero", + "config": { + "show_current_weather": true + } + }, + { + "name": "mode-current", + "standalone": false, + "config": { + "show_current_weather": true + } + }, + { + "name": "mode-hourly", + "standalone": false, + "config": { + "show_hourly_forecast": true + } + }, + { + "name": "mode-daily", + "standalone": false, + "config": { + "show_daily_forecast": true + } + }, + { + "name": "mode-almanac", + "standalone": false, + "config": { + "show_almanac": true + } + }, + { + "name": "mode-radar", + "standalone": false, + "config": { + "show_radar": true + } + }, + { + "name": "size-64x32", + "width": 64, + "height": 32, + "standalone": false, + "config": { + "show_current_weather": true + } + }, + { + "name": "size-128x32", + "width": 128, + "height": 32, + "standalone": false, + "config": { + "show_current_weather": true + } + }, + { + "name": "size-128x64", + "width": 128, + "height": 64, + "standalone": false, + "config": { + "show_current_weather": true + } + }, + { + "name": "size-256x32", + "width": 256, + "height": 32, + "standalone": false, + "config": { + "show_current_weather": true + } + } + ], + "composites": [ + { + "name": "modes", + "columns": 1, + "cells": [ + { + "shot": "mode-current", + "label": "Current conditions" + }, + { + "shot": "mode-hourly", + "label": "Hourly forecast" + }, + { + "shot": "mode-daily", + "label": "Daily forecast" + }, + { + "shot": "mode-almanac", + "label": "Almanac" + }, + { + "shot": "mode-radar", + "label": "Radar" + } + ] + }, + { + "name": "panel-sizes", + "columns": 2, + "cells": [ + { + "shot": "size-64x32", + "label": "64 x 32" + }, + { + "shot": "size-128x32", + "label": "128 x 32" + }, + { + "shot": "size-128x64", + "label": "128 x 64" + }, + { + "shot": "size-256x32", + "label": "256 x 32" + } + ] + } + ] +} diff --git a/plugins.json b/plugins.json index f900d57c..7d5def65 100644 --- a/plugins.json +++ b/plugins.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "last_updated": "2026-09-02", + "last_updated": "2026-09-03", "plugins": [ { "id": "cricket-scoreboard", @@ -928,10 +928,10 @@ "repo": "https://github.com/ChuckBuilds/ledmatrix-plugins", "branch": "main", "plugin_path": "plugins/ledmatrix-weather", - "latest_version": "2.6.5", + "latest_version": "2.6.6", "stars": 0, "downloads": 0, - "last_updated": "2026-08-05", + "last_updated": "2026-09-03", "verified": true, "screenshot": "" }, diff --git a/plugins/ledmatrix-weather/README.md b/plugins/ledmatrix-weather/README.md index a22fad8f..81d55209 100644 --- a/plugins/ledmatrix-weather/README.md +++ b/plugins/ledmatrix-weather/README.md @@ -13,6 +13,13 @@ # Weather Display Plugin +![Current conditions on a 128x32 panel: a cloud icon, "Partly Cloudy", the +temperature, the day's high and low, and UV, humidity, wind and feels-like +readings](../../docs/assets/ledmatrix-weather/hero.png) + +*Every image in this README is real plugin output, rendered from a recorded +Open-Meteo response for Dallas on 2026-06-14 so it reproduces exactly.* + Comprehensive weather display plugin for LEDMatrix showing current conditions, hourly forecast, and daily forecast. Current Weather: @@ -60,6 +67,8 @@ generated from it. The keys you'll touch most often: | Key | Default | Notes | |---|---|---| | `enabled` | `false` | Master switch | +| `location_latitude` / `location_longitude` | `null` | Set both to skip geocoding and pin an exact position. Left null, the city/state/country above are geocoded once and cached. Advanced | +| `api_key` | `null` | Not needed for the default setup — Open-Meteo requires no key. Kept for deployments that proxy a keyed weather service. Advanced | | `location_city` | `"Dallas"` | City name | | `location_state` | `"Texas"` | State/province (optional, helps US disambiguation) | | `location_country` | `"US"` | ISO 3166-1 alpha-2 code | @@ -75,7 +84,7 @@ generated from it. The keys you'll touch most often: | `show_alerts` | `true` | Show active weather alerts (preempts rotation, US only) | | `show_feels_like` / `show_dew_point` / `show_visibility` / `show_pressure` | `true` | Extra current-conditions metrics (need height ≥ 48px) | | `radar_map_style` | `"osm"` | Basemap: `osm`, `carto`, `carto_dark`, `esri` (real tiles, worldwide) or `vector` (retro WeatherStar state outlines, US-only). Tile styles fall back to the vector map automatically when tiles can't be fetched | -| `radar_range_miles` | `50` | Distance from your location to the panel edge (10–500). Replaces the deprecated `radar_zoom`, which is still honored for old configs | +| `radar_range_miles` | `75` | Distance from your location to the panel edge (10–500). Replaces the deprecated `radar_zoom`, which is still honored for old configs | | `radar_show_nowcast` | `true` | Append ~30 min of predicted radar (`FCST +10m` … with yellow dots) after the observed frames | | `radar_tile_server` | `"https://maps.chuck-builds.com"` | Self-hosted OSM tile server for the `osm` style (`{server}/tile/{z}/{x}/{y}.png`); public mirrors are the fallback | | `radar_map_brightness` | `0.5` | Dim the tile basemap so precipitation pops on the matrix | @@ -84,7 +93,8 @@ generated from it. The keys you'll touch most often: | `radar_update_interval` | `180` | Seconds between new-frame checks (60–1800). Checks are cheap; tiles only download when RainViewer publishes a new frame | | `radar_past_frames` | `6` | Observed frames to animate (~10 min apart) | | `radar_frame_seconds` / `radar_loop_pause_seconds` | `0.5` / `1.5` | Animation pacing: per-frame time and the hold on the newest frame | -| `dynamic_duration` | `{"enabled": false}` | Opt-in: hold the radar until a full animation loop completes (capped by `max_duration_seconds`) | +| `dynamic_duration.enabled` | `false` | Opt-in: hold the radar until a full animation loop completes rather than cutting mid-loop | +| `dynamic_duration.max_duration_seconds` | `60` | Ceiling on that hold, in seconds (10–300), so a slow loop cannot monopolise the panel | ## Display modes @@ -99,6 +109,20 @@ controller rotates through them in order: | `almanac` | Sunrise, sunset, moon phase, day length | | `radar` | Animated precipitation radar (RainViewer) over an OSM or vector basemap, with optional nowcast frames | +![The five display modes](../../docs/assets/ledmatrix-weather/modes.png) + +The radar panel above is rendered with no network access, so it shows the +overlay — the location crosshair and the frame timestamp — over an empty +basemap. On a connected display the basemap tiles and the precipitation frames +fill it; those come from live tile servers, which is why this one screen cannot +be reproduced from a recording the way the others are. + +Every mode adapts to the panel. The current-conditions screen adds the +feels-like, dew point, visibility and pressure readings only when the panel is +at least 48px tall: + +![Current conditions on four panel sizes](../../docs/assets/ledmatrix-weather/panel-sizes.png) + When an active weather alert is available and `show_alerts` is true, the alert takes priority over the normal rotation. Alerts are sourced from the [NWS API](https://www.weather.gov/documentation/services-web-api) and are diff --git a/plugins/ledmatrix-weather/manifest.json b/plugins/ledmatrix-weather/manifest.json index 0284ec87..3ef199ce 100644 --- a/plugins/ledmatrix-weather/manifest.json +++ b/plugins/ledmatrix-weather/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-weather", "name": "Weather Display", - "version": "2.6.5", + "version": "2.6.6", "author": "ChuckBuilds", "class_name": "WeatherPlugin", "update_interval": 60, @@ -26,6 +26,12 @@ "radar" ], "versions": [ + { + "version": "2.6.6", + "released": "2026-09-03", + "notes": "Radar overlay font, and documentation. weather_radar.py resolved the 4x6 face three levels above itself, which is only the LEDMatrix root when the plugins directory sits inside the core tree. Anywhere else it fell back to PIL's default font -- taller than the 6px the overlay is laid out for -- and the radar timestamp label was drawn past the bottom edge of the panel, where the safety harness cannot see it because PIL clips at draw time. It now asks the imported core where it lives. The README gains real rendered screenshots of all five display modes and four panel sizes, documents api_key, location_latitude/longitude and both dynamic_duration keys, and corrects the radar_range_miles default, which the table gave as 50 where the schema says 75.", + "ledmatrix_min_version": "2.0.0" + }, { "version": "2.6.5", "released": "2026-08-21", @@ -162,7 +168,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-08-05", + "last_updated": "2026-09-03", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/ledmatrix-weather/weather_radar.py b/plugins/ledmatrix-weather/weather_radar.py index 307f6029..3be7be41 100644 --- a/plugins/ledmatrix-weather/weather_radar.py +++ b/plugins/ledmatrix-weather/weather_radar.py @@ -42,7 +42,26 @@ # Core-repo font, resolved relative to this file (plugin dir -> plugins -> root), # matching how manager.py locates assets. CWD-independent. -_FONT_PATH = Path(__file__).resolve().parent.parent.parent / "assets" / "fonts" / "4x6-font.ttf" +def _font_candidates(): + """Where the 4x6 face may live, best guess first. + + The plugin normally sits at /plugin-repos/, so three levels + up is the core root -- but the plugins directory is configurable, and from + anywhere else that guess finds nothing. The fallback is PIL's default font, + which is taller than the 6px this overlay is laid out for, so the timestamp + label ends up drawn past the bottom edge of the panel. Ask the imported + core where it is first. + """ + roots = [] + try: + import src # the LEDMatrix core package + if getattr(src, "__file__", None): + roots.append(Path(src.__file__).resolve().parent.parent) + except Exception: # nosec B110 - absence is expected off a real install + pass + roots.append(Path(__file__).resolve().parent.parent.parent) + roots.append(Path.cwd()) + return [r / "assets" / "fonts" / "4x6-font.ttf" for r in roots] _FETCH_BUDGET_SECONDS = 20 # per refresh_data() call; work resumes next tick _NOWCAST_FRAMES = 3 # RainViewer publishes 3 nowcast frames (10/20/30 min) @@ -560,9 +579,13 @@ def get_radar_image(self, width: int, height: int) -> Optional[Image.Image]: def _load_font(self): if self._font is None: - try: - self._font = ImageFont.truetype(str(_FONT_PATH), 6) - except (OSError, ValueError): + for candidate in _font_candidates(): + try: + self._font = ImageFont.truetype(str(candidate), 6) + break + except (OSError, ValueError): + continue + if self._font is None: self._font = ImageFont.load_default() return self._font diff --git a/scripts/docs_render_support/sitecustomize.py b/scripts/docs_render_support/sitecustomize.py index 20844d5e..90fe76c1 100644 --- a/scripts/docs_render_support/sitecustomize.py +++ b/scripts/docs_render_support/sitecustomize.py @@ -52,3 +52,65 @@ def today(cls): _docs_http_replay.install() except Exception: # never let doc tooling break the render it is measuring pass + + +# Some plugins hold their interesting state in memory, put there by an event +# the renderer cannot produce -- an MQTT message, a webhook, a button press. +# No configuration reaches that state, so a documentation render would only +# ever show the idle frame. LEDMATRIX_DOCS_ATTRS names attributes to set on the +# plugin instance once the core's loader has built it, which is the same thing +# the event would have done, without the plugin knowing it is being rendered. +_ATTRS = os.environ.get("LEDMATRIX_DOCS_ATTRS") +if _ATTRS: + import json as _json + import sys as _sys + + _WANTED = _json.loads(_ATTRS) + _TARGET = "src.plugin_system.plugin_loader" + + def _apply(instance): + for name, value in _WANTED.items(): + if isinstance(value, list) and len(value) == 3 and all( + isinstance(v, int) for v in value): + value = tuple(value) # colours are tuples everywhere in the core + setattr(instance, name, value) + return instance + + class _PatchingLoader: + """Wraps the real loader so the module is patched right after it runs.""" + + def __init__(self, inner): + self._inner = inner + + def create_module(self, spec): + return self._inner.create_module(spec) + + def exec_module(self, module): + self._inner.exec_module(module) + real = module.PluginLoader.load_plugin + + def load_plugin(self, *args, **kwargs): + instance, mod = real(self, *args, **kwargs) + return _apply(instance), mod + + module.PluginLoader.load_plugin = load_plugin + + def __getattr__(self, name): + return getattr(self._inner, name) + + class _AttrFinder: + """Hands back the real spec with its loader wrapped, once.""" + + def find_spec(self, name, path=None, target=None): + if name != _TARGET: + return None + index = _sys.meta_path.index(self) + for finder in _sys.meta_path[index + 1:]: + find = getattr(finder, "find_spec", None) + spec = find(name, path, target) if find else None + if spec is not None and spec.loader is not None: + spec.loader = _PatchingLoader(spec.loader) + return spec + return None + + _sys.meta_path.insert(0, _AttrFinder()) diff --git a/scripts/render_docs_assets.py b/scripts/render_docs_assets.py index 42a1d93d..36c7bd4b 100644 --- a/scripts/render_docs_assets.py +++ b/scripts/render_docs_assets.py @@ -263,12 +263,16 @@ def render_shot( # clock, a countdown, or a "starts in 2h" line differs on every run. freeze_time = shot.get("freeze_time", defaults.get("freeze_time")) http_replay = shot.get("http_replay", defaults.get("http_replay")) - if freeze_time or http_replay: + # Runtime state that an event would normally have set; see the shim. + attrs = shot.get("attrs", defaults.get("attrs")) + if freeze_time or http_replay or attrs: support_dir = str(Path(__file__).resolve().parent / "docs_render_support") existing = env.get("PYTHONPATH") env["PYTHONPATH"] = f"{support_dir}{os.pathsep}{existing}" if existing else support_dir if freeze_time: env["LEDMATRIX_DOCS_FREEZE_TIME"] = str(freeze_time) + if attrs: + env["LEDMATRIX_DOCS_ATTRS"] = json.dumps(attrs) if http_replay: replay_path = (shot_list_dir / http_replay).resolve() if not replay_path.is_file():