From 85154f59760b7b8c285e3126c906dc23ffb3a9c7 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Sat, 3 Oct 2026 11:41:49 -0400 Subject: [PATCH 1/2] Plugin-wide bug and docs pass: 41 plugins patched, repo docs corrected Bug fixes: - cricket: innings parsing (bowling innings, 0/0 for sides yet to bat, double highlight), winner flag read as truthy string, Test day/status line, chase line overflow, late live pickup; harness fixture now offline - of-the-day: IndexError after midnight rollover; upload path traversal; day 366 accepted by save/upload scripts - ledmatrix-elections: called-race snapshot expired after 5 min (lost on restart); malformed calendar_events date broke every update - pomodoro-timer: inf/overflow durations wedged the timer - on-air: republish retained state on connect; real sw_version - mqtt-notifications: BDF fonts never rendered; image retry per frame - tide-display: honour the core's display_mode (disabled modes took a turn) - news: empty failed the whole feed - calendar: sort events by real start time across calendars - static-image: error screen drew on the discarded image; zero-height fit - countdown: oversized image-right pushed text off panel - soccer custom leagues, f1 auto_scale, youtube-stats cache key, music album-art cache, stocks switch-mode layout, blackjack color-picker, christmas-countdown "1 DAY", hello-world font-manager None check, birdnet photo retry, basketball/baseball missing layout settings Docs: READMEs reconciled with schemas and code across all bumped plugins; repo docs fixed (required manifest fields, new plugins need a hand-added plugins.json entry, core API signatures, CI workflow names); README screenshots regenerated for christmas-countdown, hello-world, 7-segment-clock and ledmatrix-stocks. scripts/test_stage3_mixin_copies.py: list the three SportsFetchMixin methods core added in LEDMatrix#728. Co-Authored-By: Claude Opus 5.5 --- .github/PULL_REQUEST_TEMPLATE.md | 9 +- CLAUDE.md | 9 +- CONTRIBUTING.md | 42 +- README.md | 6 +- SUBMISSION.md | 15 +- VERIFICATION.md | 10 +- docs/assets/7-segment-clock/colors.png | Bin 16652 -> 17125 bytes docs/assets/7-segment-clock/digit-spacing.png | Bin 29333 -> 30561 bytes docs/assets/7-segment-clock/panel-sizes.png | Bin 30513 -> 31455 bytes docs/assets/7-segment-clock/separator.png | Bin 7347 -> 7823 bytes docs/assets/7-segment-clock/shots.json | 4 +- docs/assets/7-segment-clock/time-formats.png | Bin 15073 -> 15436 bytes docs/assets/christmas-countdown/countdown.png | Bin 20386 -> 20705 bytes docs/assets/christmas-countdown/hero.png | Bin 2986 -> 2837 bytes .../christmas-countdown/panel-sizes.png | Bin 31462 -> 31537 bytes .../assets/christmas-countdown/text-color.png | Bin 15483 -> 14534 bytes docs/assets/hello-world/colors.png | Bin 12623 -> 13384 bytes docs/assets/hello-world/hero.png | Bin 927 -> 1028 bytes docs/assets/hello-world/message-length.png | Bin 15906 -> 16573 bytes docs/assets/hello-world/panel-sizes.png | Bin 20327 -> 22048 bytes docs/assets/hello-world/shots.json | 4 +- docs/assets/hello-world/show-time.png | Bin 7788 -> 8147 bytes docs/assets/ledmatrix-stocks/display-mode.png | Bin 14462 -> 14741 bytes docs/assets/ledmatrix-stocks/gain-loss.png | Bin 19203 -> 19032 bytes docs/assets/ledmatrix-stocks/hero.png | Bin 1961 -> 2062 bytes docs/assets/ledmatrix-stocks/panel-sizes.png | Bin 17017 -> 17133 bytes docs/assets/ledmatrix-stocks/toggle-chart.png | Bin 8289 -> 9492 bytes docs/plugin-development/01-plugin-anatomy.md | 11 +- docs/plugin-development/02-core-api.md | 24 +- .../03-advanced-features.md | 38 +- .../04-styling-and-skins.md | 23 +- docs/plugin-development/05-adaptive-layout.md | 5 +- .../06-manifest-and-config-schema.md | 24 +- .../07-testing-ci-and-registry.md | 10 +- docs/plugin-development/README.md | 1 + plugins.json | 265 +++++------- plugins/7-segment-clock/README.md | 23 +- plugins/7-segment-clock/manifest.json | 10 +- plugins/afl-scoreboard/CHANGELOG.md | 11 + plugins/afl-scoreboard/README.md | 12 +- plugins/afl-scoreboard/manifest.json | 10 +- plugins/afl-scoreboard/test_afl_plugin.py | 9 + plugins/baseball-scoreboard/CHANGELOG.md | 16 + plugins/baseball-scoreboard/README.md | 18 +- .../baseball-scoreboard/config_schema.json | 28 +- plugins/baseball-scoreboard/manifest.json | 10 +- plugins/basketball-scoreboard/CHANGELOG.md | 19 + plugins/basketball-scoreboard/README.md | 13 +- .../basketball-scoreboard/config_schema.json | 62 ++- plugins/basketball-scoreboard/manifest.json | 8 +- plugins/birdnet-go/README.md | 2 +- plugins/birdnet-go/manager.py | 20 +- plugins/birdnet-go/manifest.json | 10 +- plugins/blackjack/config_schema.json | 6 +- plugins/blackjack/manifest.json | 8 +- plugins/calendar/README.md | 38 +- plugins/calendar/manager.py | 31 +- plugins/calendar/manifest.json | 8 +- plugins/christmas-countdown/README.md | 55 ++- plugins/christmas-countdown/manager.py | 13 +- plugins/christmas-countdown/manifest.json | 10 +- plugins/clock-simple/README.md | 38 +- plugins/clock-simple/manifest.json | 10 +- plugins/countdown/README.md | 15 +- plugins/countdown/manager.py | 8 +- plugins/countdown/manifest.json | 10 +- plugins/countdown/test_layout_overrides.py | 8 + plugins/cricket-scoreboard/README.md | 20 +- plugins/cricket-scoreboard/config_schema.json | 3 +- .../cricket_data_fetcher.py | 8 +- .../cricket-scoreboard/cricket_renderer.py | 114 +++-- plugins/cricket-scoreboard/manager.py | 32 +- plugins/cricket-scoreboard/manifest.json | 10 +- .../test/fixtures/mock.json | 398 ++++++++++++++++++ plugins/cricket-scoreboard/test/harness.json | 5 +- plugins/f1-scoreboard/README.md | 26 +- plugins/f1-scoreboard/f1_renderer.py | 16 +- plugins/f1-scoreboard/manifest.json | 10 +- plugins/football-scoreboard/CHANGELOG.md | 7 + plugins/football-scoreboard/README.md | 2 + plugins/football-scoreboard/manifest.json | 8 +- plugins/hello-world/README.md | 21 +- plugins/hello-world/manager.py | 14 +- plugins/hello-world/manifest.json | 10 +- plugins/hockey-scoreboard/CHANGELOG.md | 9 + plugins/hockey-scoreboard/README.md | 4 +- plugins/hockey-scoreboard/manifest.json | 8 +- plugins/hockey-scoreboard/requirements.txt | 3 - plugins/incoming-packages/README.md | 4 +- plugins/incoming-packages/manifest.json | 10 +- plugins/jellyfin-now-playing/CHANGELOG.md | 7 + plugins/jellyfin-now-playing/README.md | 2 +- plugins/jellyfin-now-playing/manager.py | 2 +- plugins/jellyfin-now-playing/manifest.json | 10 +- plugins/lacrosse-scoreboard/CHANGELOG.md | 10 + plugins/lacrosse-scoreboard/README.md | 5 +- .../lacrosse-scoreboard/config_schema.json | 5 +- plugins/lacrosse-scoreboard/manifest.json | 8 +- plugins/ledmatrix-elections/README.md | 38 +- .../ledmatrix-elections/config_schema.json | 2 +- plugins/ledmatrix-elections/manager.py | 9 +- plugins/ledmatrix-elections/manifest.json | 10 +- plugins/ledmatrix-elections/store.py | 7 +- plugins/ledmatrix-elections/test/harness.json | 3 +- plugins/ledmatrix-elections/test_calendar.py | 28 +- plugins/ledmatrix-leaderboard/README.md | 2 +- plugins/ledmatrix-leaderboard/manifest.json | 10 +- plugins/ledmatrix-music/CHANGELOG.md | 9 + plugins/ledmatrix-music/manager.py | 13 +- plugins/ledmatrix-music/manifest.json | 10 +- plugins/ledmatrix-stocks/README.md | 6 +- plugins/ledmatrix-stocks/display_renderer.py | 30 +- plugins/ledmatrix-stocks/manifest.json | 10 +- plugins/ledmatrix-weather/CHANGELOG.md | 6 + plugins/ledmatrix-weather/README.md | 2 + plugins/ledmatrix-weather/manifest.json | 10 +- plugins/march-madness/README.md | 7 +- plugins/march-madness/manifest.json | 10 +- plugins/masters-tournament/README.md | 12 +- plugins/masters-tournament/manifest.json | 10 +- plugins/mqtt-notifications/README.md | 6 +- plugins/mqtt-notifications/manager.py | 34 +- plugins/mqtt-notifications/manifest.json | 10 +- plugins/mqtt-notifications/requirements.txt | 1 - plugins/news/manager.py | 5 +- plugins/news/manifest.json | 10 +- plugins/nfl-draft/README.md | 8 +- plugins/nfl-draft/manifest.json | 10 +- plugins/nrl-scoreboard/CHANGELOG.md | 13 + plugins/nrl-scoreboard/README.md | 14 +- plugins/nrl-scoreboard/manifest.json | 10 +- plugins/odds-ticker/README.md | 24 +- plugins/odds-ticker/manifest.json | 10 +- plugins/of-the-day/README.md | 51 ++- plugins/of-the-day/config_schema.json | 4 +- plugins/of-the-day/manager.py | 7 +- plugins/of-the-day/manifest.json | 10 +- plugins/of-the-day/scripts/save_file.py | 8 +- plugins/of-the-day/scripts/upload_file.py | 17 +- .../of-the-day/test_leap_day_and_rollover.py | 25 +- plugins/olympics/README.md | 27 +- plugins/olympics/manifest.json | 8 +- plugins/on-air/README.md | 8 +- plugins/on-air/config_schema.json | 2 +- plugins/on-air/manager.py | 20 +- plugins/on-air/manifest.json | 10 +- plugins/pomodoro-timer/README.md | 4 +- plugins/pomodoro-timer/config_schema.json | 2 +- plugins/pomodoro-timer/manager.py | 21 +- plugins/pomodoro-timer/manifest.json | 10 +- plugins/pomodoro-timer/test_pomodoro_timer.py | 18 + plugins/soccer-scoreboard/CHANGELOG.md | 16 + plugins/soccer-scoreboard/README.md | 5 +- plugins/soccer-scoreboard/config_schema.json | 2 +- plugins/soccer-scoreboard/manager.py | 4 +- plugins/soccer-scoreboard/manifest.json | 10 +- plugins/static-image/README.md | 14 +- plugins/static-image/manager.py | 19 +- plugins/static-image/manifest.json | 10 +- .../static-image/test_error_screen_and_fit.py | 104 +++++ plugins/stock-news/README.md | 2 +- plugins/stock-news/manifest.json | 10 +- plugins/text-display/README.md | 31 +- plugins/text-display/manifest.json | 10 +- plugins/tide-display/README.md | 2 +- plugins/tide-display/manager.py | 27 +- plugins/tide-display/manifest.json | 10 +- plugins/tide-display/test_units_in_cache.py | 4 + plugins/ufc-scoreboard/CHANGELOG.md | 11 + plugins/ufc-scoreboard/README.md | 7 +- plugins/ufc-scoreboard/config_schema.json | 4 +- plugins/ufc-scoreboard/manifest.json | 8 +- plugins/youtube-stats/manager.py | 4 +- plugins/youtube-stats/manifest.json | 10 +- scripts/test_stage3_mixin_copies.py | 1 + 175 files changed, 2143 insertions(+), 663 deletions(-) create mode 100644 plugins/cricket-scoreboard/test/fixtures/mock.json create mode 100644 plugins/static-image/test_error_screen_and_fit.py diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index eddfc650..a6a73d09 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -43,8 +43,8 @@ checked box. --> forgetting this means users won't receive the change) - [ ] `class_name` in `manifest.json` matches the actual class in `manager.py` exactly (case-sensitive, no spaces) -- [ ] `entry_point` matches the real file (or is omitted to use - the `manager.py` default) +- [ ] `entry_point` matches the real file (the core manifest schema + requires it) - [ ] Updated the plugin's `README.md` if config keys changed - [ ] `config_schema.json` is the source of truth for the web UI form — any new option is in the schema with a `default`, @@ -57,7 +57,10 @@ checked box. --> - [ ] Plugin id matches the directory name and is unique - [ ] `manifest.json` has all required fields (`id`, `name`, - `version`, `class_name`, `display_modes`) + `version`, `author`, `entry_point`, `class_name`, + `compatible_versions`, plus `display_modes`) +- [ ] Added the plugin's entry to `plugins.json` (new plugins are the + one hand-added case; `update_registry.py --check` fails without it) - [ ] `manager.py` inherits from `BasePlugin` and implements `update()` and `display()` - [ ] `config_schema.json` exists and validates as JSON Schema Draft-7 diff --git a/CLAUDE.md b/CLAUDE.md index 6299fbaa..62b6db5f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,6 +37,9 @@ These are not general Python advice — they exist because of how *this* stack w 2. **Never hand-edit `plugins.json`.** Commit with the pre-commit hook (`cp scripts/pre-commit .git/hooks/pre-commit`) or run `python update_registry.py`. CI also regenerates on push to `main`. + Exception: a **new** plugin's entry (and review fields like `verified`) is + added by hand — the script only updates existing entries, and `--check` + fails without one (docs topic 07). 3. **Fetch in `update()`, draw in `display()`.** Never hit the network from `display()`. Cache network data via `self.cache_manager`, keys namespaced by plugin id. @@ -105,8 +108,10 @@ Plugin class: subclass `BasePlugin` from the core (`src.plugin_system.base_plugin.BasePlugin`). Constructor args: `plugin_id, config, display_manager, cache_manager, plugin_manager`. -Required manifest fields: `id` (matches directory), `name`, `version`, -`class_name`, `display_modes`. Full field list / schema conventions → +Required manifest fields (core `schema/manifest_schema.json`, validated in CI): +`id` (matches directory), `name`, `version`, `author`, `entry_point`, +`class_name`, `compatible_versions` — plus `display_modes`, which every plugin +needs to be shown. Full field list / schema conventions → `docs/plugin-development/06-manifest-and-config-schema.md`. Config schemas are JSON Schema Draft-07 with UI `x-*` extensions diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 339a6431..10832349 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -109,8 +109,10 @@ short version: - `class_name` must match the actual class name in `manager.py` **exactly** (case-sensitive, no spaces — see [VERIFICATION.md](VERIFICATION.md) for why this matters) - - Set `entry_point` (defaults to `manager.py` if omitted) - - Set `version`, `author`, `category`, `tags`, `display_modes` + - Set `entry_point` (usually `manager.py`; the core manifest schema CI + validates against requires it) + - Set `version`, `author`, `compatible_versions`, `category`, `tags`, + `display_modes` 4. Implement `update()` and `display()` in your plugin class. 5. Define the configuration schema in `config_schema.json`. The web UI form is generated automatically from this — every key you want @@ -119,8 +121,12 @@ short version: 6. Write a `README.md` covering: what the plugin does, install, configuration, and any external service / API key requirements. 7. Add a `LICENSE` (usually a copy of the project GPL-3.0). -8. Test locally via the symlink + dev_server flow above. -9. Open a PR. +8. Add the plugin's entry to `plugins.json` by hand (copy a neighbour's + shape, `plugin_path: "plugins/"`) — `update_registry.py` only + updates existing entries, and CI's `update_registry.py --check` fails a + new plugin directory without one. +9. Test locally via the symlink + dev_server flow above. +10. Open a PR. ## Commit message convention @@ -136,9 +142,11 @@ specific plugin later. ## Testing -Per-plugin tests live in the LEDMatrix repo at `test/plugins/`. If -you're adding a test for your plugin, open a corresponding PR in -LEDMatrix. The dev preview server (`scripts/dev_server.py` in +Per-plugin unit tests live with the plugin, as `plugins//test_*.py` (or +under its `test/` directory); CI runs them with `scripts/run_plugin_tests.py` +for every plugin a PR changes. Safety-harness fixtures and golden images go in +`plugins//test/` (see +[topic 7](docs/plugin-development/07-testing-ci-and-registry.md)). The dev preview server (`scripts/dev_server.py` in LEDMatrix) is the fastest way to iterate visually — its **All Sizes** button renders your plugin at every harness panel size side by side. @@ -148,7 +156,7 @@ check but fails users. Use the adaptive layout system (`docs/ADAPTIVE_LAYOUT.md` in LEDMatrix: `self.layout`, `draw_fit`, `draw_image`, `scoreboard_regions`) and check the harness's `fill warn` output in `check_plugin.py` reports. Declare your layout's -design size in the manifest (`"display": {"design_size": ...}`) and, once +design size in the manifest (`"display": {"design_size": {"width": 128, "height": 32}}`) and, once your plugin is adaptive, opt into strict checking via `test/harness.json`: `{"fill_check": "strict"}`. See [docs/plugin-development/05-adaptive-layout.md](docs/plugin-development/05-adaptive-layout.md). @@ -157,12 +165,18 @@ your plugin is adaptive, opt into strict checking via - **Plugin Safety** (`test-plugins.yml`): for each changed plugin, enforces the version bump, validates `manifest.json` against the core schema, installs its - `requirements.txt`, and runs the safety harness across all matrix sizes. -- **Module Collisions** (`module-collisions.yml`): runs - `check_module_collisions.py` across all plugins. - -A changed plugin whose code (anything outside `test/`, including its README) is -not accompanied by a `version` bump **fails the PR**. + `requirements.txt`, runs the safety harness across all matrix sizes, and runs + its unit tests; on every run it also checks `plugins.json` against the + manifests (`update_registry.py --check`) and runs every `scripts/test_*.py`. +- **Plugin Structure** (`module-collisions.yml`): runs + `check_module_collisions.py` and the other all-plugin structural checks. +- **Sports Lineage Drift** (`sports-drift.yml`): on scoreboard changes, fails + when copies of a shared sports function that agreed start to disagree. + +A changed plugin whose code (anything outside `test/` and root-level +`test_*.py`, including its README) is not accompanied by a `version` bump +**fails the PR**. Details: +[topic 7](docs/plugin-development/07-testing-ci-and-registry.md#ci-workflows). ## Code of Conduct diff --git a/README.md b/README.md index 777859c0..60dd8d31 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \ | [Masters Tournament](./plugins/masters-tournament/) | Live Masters golf leaderboard, hole tracking, player cards | masters-tournament on an LED panel | | [NFL Draft](./plugins/nfl-draft/) | Projected & live NFL draft picks from ESPN | nfl-draft on an LED panel | | [March Madness](./plugins/march-madness/) | NCAA tournament bracket tracker with round branding and live scores | march-madness on an LED panel | -| [NFL Stat Leaders](./plugins/nfl-stat-leaders/) | Scrolling NFL statistical leaderboards: passing, rushing & receiving yards and TDs | nfl-stat-leaders on an LED panel | +| [NFL Stat Leaders](./plugins/nfl-stat-leaders/) | Scrolling NFL statistical leaderboards: passing, rushing & receiving yards and TDs, plus receptions, sacks, INTs, tackles & passer rating | nfl-stat-leaders on an LED panel | | [Fantasy Blitz](./plugins/fantasy-blitz/) | Arcade-style NFL fantasy football: top scorers as player cards, big plays, busts, waiver pickups and injuries | fantasy-blitz on an LED panel | | [Sports Leaderboard](./plugins/ledmatrix-leaderboard/) | League standings, rankings, conference records | ledmatrix-leaderboard on an LED panel | | [Olympics Countdown](./plugins/olympics/) | Countdown to next Olympics with live medal counts | olympics on an LED panel | @@ -354,7 +354,11 @@ Optional but recommended: |-------|------|-------------| | `id` | string | Unique plugin identifier | | `name` | string | Human-readable name | +| `version` | string | Semver, e.g. `1.0.0` | +| `author` | string | Plugin author | +| `entry_point` | string | Python file holding the plugin class, e.g. `manager.py` | | `class_name` | string | Plugin class name (must match class in entry point) | +| `compatible_versions` | array | LEDMatrix version constraints, e.g. `[">=2.0.0"]` | | `display_modes` | array | Display mode names | See the [manifest schema](https://github.com/ChuckBuilds/LEDMatrix/blob/main/schema/manifest_schema.json) for complete field reference. diff --git a/SUBMISSION.md b/SUBMISSION.md index 13862e98..915bcc80 100644 --- a/SUBMISSION.md +++ b/SUBMISSION.md @@ -7,7 +7,9 @@ Want to add your plugin to the official registry? Follow these steps! Before submitting, ensure your plugin: - ✅ Has a complete `manifest.json` with all required fields - (`id`, `name`, `version`, `class_name`, `display_modes`) + (`id`, `name`, `version`, `author`, `entry_point`, `class_name`, + `compatible_versions` — the core manifest schema requires these — plus + `display_modes`) - ✅ Follows the [plugin development guide](docs/plugin-development/) - ✅ Has comprehensive README documentation - ✅ Includes example configuration @@ -36,10 +38,17 @@ Submit a PR to add your plugin directly to this repository: README.md ``` -3. **Submit Pull Request** +3. **Add a `plugins.json` entry** + `update_registry.py` only updates entries that already exist, so a new + plugin's entry is the one thing added by hand: copy a neighbour's shape with + `plugin_path: "plugins/your-plugin-id"`. CI (`update_registry.py --check`) + fails a PR that adds a plugin directory without one. See + [the registry](docs/plugin-development/07-testing-ci-and-registry.md#the-registry-pluginsjson). + +4. **Submit Pull Request** Create PR with title: "Add plugin: your-plugin-name" -After approval, your plugin will be added to `plugins.json` and available in the Plugin Store. +After approval and merge, your plugin is available in the Plugin Store. ### Option B: Keep Your Own Repository (3rd-Party) diff --git a/VERIFICATION.md b/VERIFICATION.md index 8a91eac1..478ec281 100644 --- a/VERIFICATION.md +++ b/VERIFICATION.md @@ -18,14 +18,16 @@ module-collision check. Use this list for the human judgment CI can't make ## Manifest Validation -- [ ] All required fields present (`id`, `name`, `version`, `class_name`, - `display_modes`) +- [ ] All required fields present (`id`, `name`, `version`, `author`, + `entry_point`, `class_name`, `compatible_versions` per the core + manifest schema, plus `display_modes`) - [ ] `class_name` matches the actual class name in the entry point (case-sensitive, no spaces) — the loader does `getattr(module, class_name)` and will fail with `AttributeError` otherwise -- [ ] `entry_point` either matches the real file name or is omitted - (defaults to `manager.py`) +- [ ] `entry_point` matches the real file name (the loader defaults to + `manager.py` when it is absent, but the core manifest schema CI + validates against requires it) - [ ] `id` matches the directory name - [ ] Valid JSON syntax - [ ] Correct version format (semver) diff --git a/docs/assets/7-segment-clock/colors.png b/docs/assets/7-segment-clock/colors.png index b52e2f3ffb54b32f34d3b62044af6dd400d47dd8..eef9b9b810a91289aa804dd8b382789a1a7047b3 100644 GIT binary patch literal 17125 zcmb_^2UL?;w{{#qWw5{?DuW1&R0Rc<-V#TtQlipCN=9kYrMEzwQIUX%p+khIH0jc% zMnQTBMS6+U(0dDM|A{*Hj`PpD<6ZyHTCA76kaNzv&pvxUdp}P=U(?e#eBi_Z2n2Fi zOH=g*1hQWh0{P*2;_3Mmg=ttK7)%R{?84ke9tfV6K=C}-j+xI`0&9q zfdj03uUM}?^tr_0el5aY`mrnv`uDroTFRC`$_`&N?SQo}y0GU$Mt**Ny#U(jM#ql= zS5r^g<5({!Su?LWX>|x{U8Z4{h2LzdcwVL3bZh*%)t)TlQa|c3W3znwp3KsgJrGD@ z&U!(Hjp*}xHI=^W0^?FRa%*xpG(o{oQ$xs*Ql(NRnwO)s(BbH2Brpx}&tnpKF2p`jsdcN44Pza<#1T!2D$r|EKF zTBe-KXVdiIDxU4n6|#(SPQU`Gt?^QD`LT!KkqR<0G8W!v4c2qFSZ` zY4eCuzX2g3tbOL*BjG6OMDR|VOJH|L$I50NiuT;S-K6O5^ XBgil1Bz(N* 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 literal 16652 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 dA@$$#1hZ;-9>!k1`bm;)bkkI$@ diff --git a/docs/assets/7-segment-clock/digit-spacing.png b/docs/assets/7-segment-clock/digit-spacing.png index afec5c2210c485d2c54b72bbd0c905064cf6d889..3d3d3ce7c1d5c5f194bc8db24263737031066c32 100644 GIT binary patch literal 30561 zcmd43cUY9y*0?>!C`1$xg9Z_hq9|2CK)M17f)wdUQ=~|j-e-)W2#7cok#3=bltFqK z8`44P9qBOi-llx(QS;`QlQ-x5-oL(Fxvpdwc*@>uuX?W)sG+8~Z_nX9C=_bnB_#z- z6l%8u3bpeb^-u7V)ru$lD3r&EOA6<;J$q*RJ>s;-D$bIv3L9|BiW!_D-5(ciU;Jin2~8>3juxJfYIaG8UiR`EZ;;A!pOyF-?vF~N5!$;0`JeNrX^{Wi zF+2G0pMI|5`Grdy zyWofa_Nm9sEU!zTr}#-)u>Ecn!4}6J`x88BhuUw#mJRRj(S&1U*ZW-L@9#|?K9E`9 z#GmKW6BipR>Afnami|&P)Zx>!-=}C$tC4mYA4P21COZrrIuGcB@;20$WylPoH%iUkFT#Jq&~%|K(|2RH}|!Xy3;p48aY)lHcPMfsbjWUClY)d z7QVc_QT=H3^l{Y8K65RRFx3!dvAH%a^@{ZYAH|Tv1@5ywWpkw}(ljVST9&cj?Rr7H z?;(35m6`jZ>$AN@ilM9?gMRDb>M~4np7Ry!duV7JOU9m5`faNv%Jg&XzP+U?s>9jc z=2Xq%QF|mN~cnUo6f#8$hR~_Z;p+G%92}D1S9R=3pYy%n|*k2mrkZQH+O-JvQh1~ z-oM@`HW(pO;kUi^@?wa@TGtI`5u4Y}V|A$s-h_D!#b@eea^nCznQo2=X*gI=W^*)r zb8|D@J^>ys;-uDnT9%#?mkC(9%7u3{bh5--PEJlbrLt(^29vO*)Z&K&*zDE$L40ws z=kSNXWbCEzwLi4w%c>i?K!4+*vje5oXXPSAYSx#@8H3M2P@ZlY?HfnmYP!XZdAL^ z^?8lfVBx+EZ+_n?<~VRf?q)|;H|%bS*YddJe7Q?+p?wsrXS~P2X1~W^1!=99-QQD+ zoGMF7x9!ZPlV17s;zIBv2|^mPWBKAIdAf?t@mQ_c&)LRhy$)seGhKP6eyi=bckF}} zJ1qB`n1hi{=^)Oc9c*6I&gmJQu!jhDzar zm@yke0fQKd-%@DC>m-l)f#r$j#8cPhsIR}hv#T{#9Y6UZSh+O^L}FuWItMcm?;%G$ zU%uS5wK@$VJ;3ZxJe1%w_Y3>VbZ3rErY@)AqswTA63@lqq5 zAgERiPqDtx2EAmQ8U?a!(~a<}Pu^yTVC5-kPIahJjOI zH&`iMVEf9j2u&fabG~((?qKm-`D}R#)#ipv?|-byZ-7gh!ApCuoSl4jm(_ccy_F92 z&SYcff>pd*M~sMVidy{D!(QwC9#Mj(3GO3k(w2_j2;nLo?N{W`|LE-S19~|VlYw*) z=J)%=B4kRkwwq*=g)IJO-xJ- z55Mt0o2PBSfAw|o$RkPJEPd>jy#ng!%)Y8DwRnkFMkTnUq$Imr?HM|g_gX$6XF~i-piP0z&;Y@d% zLrscYbmH7$Nq0L3hdEWT{?|9EX}_+nenVYD!^0{w_T;9e_hQ-EApdZJDWGoqUt70M zetrpWUIj0dz4BS9CH!FoIc##*A)`}p;>R0fLz#rfdu($;h}jAkFJ9Eu)xn1v420?u zAJ63&$~;-Sd>v%WaKKmEzb4sPTI52X4dlbvp}8m>SigC zeMUmW1Plx9K0MeDXYpg$F%{5W#_l7a#zdD^QdDEtU?oAE)A=uPF^CB1TJW2c7rRU} zZO@fCS|?q9f1irg^UH8maN{*p+O)Y=8GGy=O8M%HqU_e^v@0(U^66h2-f^zWHrPPe zs>u$vFHy!%nX|%sm8W8e7+`mVyt(6=j!d=WOz!(}IF`N}3k%JJX3!3Z#wtBO(G;(K zcF132y!zzT0vnyJyYN7w{khlWt;b!LYPq>(CP z#~bJ0Xon|w9q%_5sV5{XOhtP{|1i_pTh}Zt$9Z$g&Hj`+>LDxH?QXA$`1N2@qS@L^ zw*m^bD9C*}38o<@`+gcW@3rK4e;+d>M*f>$ZY#*d$yh zK_{F6kr;jHGTtx+nr7*oaj9zwajAZJc^OQMcF#yS+t{sL zXQVom0;nR`myaZDHWr59;$EcH&ObM* zbOu)sO6z{$0dDm6Y>^_1oA!zPFE5#>QP&-LE$#b?oj_lKGrRG#@wnl#Nou85%*py_JI@P4gQT8o4xz>^po$ zztVS;v_43Iy?L>ssNR`nAbj)Fvnwwz?qidYVhznPDp`6YIrsFelUc|^o$fhDcawJj_jIyxTdj+^*}#;%_) zSd74)i0$0*tSJzz$Mqr>J9w)A7WXbV_1dCmE$SlR;I6}w;PHWtzHXD-!e>78?jAU2 zzll4KCa_Gi4fvt133A7VsqR1-lr3K~EOMBIEnT1MSIJLv=r3h){d8W3GMk!69OZWN z$ijH8@3>tNXQwQ>3pLXrnSWT!LCR_PLsnLnBCD7ASY0F){K2Q^^1>j%`wk<{hOtNA z)g^=B1cD4o!?7mCTnqKaf#32TJscQGc-RB#Qh9J(;Y5Q#d%#gxAB!@bYq{GIwqmR9 z#)e8$H`l_QMEur^h^u;Ag8e-`{P!+0PU&)nW66Pi*>CwX%My)0 z=fn`y_7%QL*-eikMC0^&50@C}mWTKC`DK@(m9AX*Txj3x?oi~z<+cMwUAAR=-^)uf z<#LQh$_Fu}lZvTv?wu?} z1rDuirPBcq{k~KN3y?2SU#|(_H1XR^9;=UH@tIlVPUzzl7GB;4)0KEAaS)Wx#vmO( zvtnUGLxa>StNH8zmW2KFC-`{cw&&vLWcBIAT}KDAvSwsb*QF<)$(u1TnZDXuYS$xk zmn=KZbNtBN!=vX1pPnXd98go9<8phD(VL?&?(!KYsm{eF8E8pZ+(WA^VTQq=H3Rg(O zhh@C*mp+ix0JbB`6V|>YO zS}2B9MIw@!9kS@P=v1~N+X3^Hh8|6~m+N|k{BK#}^CewpRz?qC(35Yej;ngEy-x6Z zBs1O>6x)+S5eMJdYXwWi=5^D~-F+>Zw8cFVz&1b%tfriGXdv(JUHRaF_H8ZVMZ@qo8G!!9Kou5I5moPK4`4Ij@FT2pwerLN^@y3v2Pt` zW;ETdE2~SDnVxS}S7V)nl}cNQF{#?U6Xh?V-4Ikg65ugS9?ud}R%Y0>Yu9|`a%j!& z?(W68{?N2*cX2_%2db*7jOnJIgog{SwnuTRZOktfHZ(nA1KDde@v*3WG|TS4kGZ26 zZEG3InyQ<-JTtTLx%!Cka!BWaz+r_Ko?EZ=^oYVcW2Ltn-@SWB|IR9r-(ldz1-^sz z_4O*-BDHP&^3?g36{j%+KHGD>xo58+I+C8ep1qA}K<6iMU74bvHNq=5}L30Go3%y`*=?+pvjw7wL_{t9d*+ps5ZLv&5Ko? zl}p)6IwfYhBmy0Zo>1Mnms!l3L2iS*Js(bS=q(jVA_~Y;=j{Sm2T7Y3L#2k41j0?K=^zqGNk^K~Uw0Xcw%bFvc(A{M|m`CaLL`^LkP% zUv^p-^_6?MT)K2A_#l7frlp%u%Zn;>tf<2tb}oi0cK?}~86uHLF9$Cb&TQ9@6Y>89 zmsy?0)3BehX-hL$K3E$nO|xfD-JH}xnqy-`vL@llzQZ#vYq!~C*CyYCVE0zaSKp-* zUkP}4ZWl?Jw>v$Ryfk5Mp9xM-_oAnRL(FlVo`kk2^Y;!m;MSN7Jc_K3602$CcdUQ+?#}vP;<}^NYI9>hxBsVduV5{; zX%VZY8TxnQsk3~58E0>tV`7Sqe59XrH{FWgKBppVN7_p`oSDv5IV;14Q;fp17fQ;tGgdTBB9UX+ zQyw!kn3(7aTE}6lyLs7tl;3FMO7)|7p8jVue(KYBL;QHJBSymY1BPNxtYfSnj9dL^ zp=T72-OpPi=!vfTK;PoHIa@S%*>82M)qAay5HlT?Np3{Dj8n0dv&q%e*T+gtl~h{A zb&n{fj?Q-F-5OG~?tQ!Yo=Q#hr+20l?ADv2*U2*-^BUT$B{j32vfF4@maFBakws(3 z<@Cy$v9BR$2#UoPa$*a0a>t(V8}i7E3_1>!!vPA$2u()gWpt(#jw>9)CEvXTwnmj9Gd?y2~Q1vj`dnJY4+nZtdtc6(5dRd<;RBYDB28;xI6em^Gie*E-br1jni zgjrBO>p-GGg`nVIPOR>$s~0%(^!XU3+S0YSmLMX$z`?}C6v@i#7OTK<#X6RThK3VM zCT%NnO>uDP zu};0f0m0h?1&F^#j_p0i-w>hca<}7^;e0_!>p-|^a-q10)f1=lx2(RVIR7!JnaT81 znW;|4hpCoi6)quY zTl0uTk^>tT7pG(n`BH3+|1SsRENn?h*%LSy*aC>qur=^_Y=PiXRY%T=yOH9JJ$7s9 zaVoRqwOwg!Yz$Fol3H$1;*yocw^*qr%1AS8Lc)(H{>naP(G_?fHFM>3%^Wiy^|{yU zqk8G)Mqir#qm)tW3je%z8?1vycPTd{=mHfWG}M^N*DSE_6=f)a6d5NLl1%IjUhq)1 zNvWVYuY0`0hnG&Y?N?!Ge=Z+zzfSkCGRs82*KV!tNQn$}=~eAyZ2WT9jZhtlml-WI zsI>TKO+tn{@>pUMdO`x!bKefsw`BAm$LYsU*F@y6t@EBdneG}3FQ>o}sKCJxSY;MD z$GvJW-SLVh@)b+#v-5W~NRh5z1^>m17yY6c=`XQ8>up#1OY-w2o3H776zAp5Y+3ct ztQuKV;t~|hY3bI~szBZDxfTCPnXF;z!o3;!kmio$*PQx~nexAU(r=_sbIWOtXx9`H za!*0UZ)LSfA3AstUR@;}Jl+CToh_4XxMNbbuXbd?W6jRJ%JILHHq;XeP@Z2jLZ*FTmO2pN3xs5UuB(LGSGY>Z(HTp_gApWA_IyCw?O@`yau&mNXY zQ1*`%gWMSQTq;IJ#tX0$X54Gx$O3eVE_zBa)HXF`yT)UA@8j(>dc3x%R3) z>rM-;C0^2z!k2B9W@I-HYjL|GvdP zF8%jU&(t0u)+RO4SE+MO*s9!){yKM3wp!R>@!Bzg;!5v9Wm4R)Ai!8!U65JsemLd24LV4yq&cf##N$9e2H+ma?U(dncmT zCV~%j)MT4$#hH9N9~R*4oQOw;V*%Q9YbyN!90W!pmYDaii4UlvPm!^@?5%*3TNffF;+#}3% z$J_X?mAAhYlRu1dmL>I!LAq^aQZ`L1Jg<^DxI0)ScXxNL7RZ$=jaJK&ow$}VO-Vcq zwav{ruFd!E-NP!Wsi|=}BS|j~Qw+)NtKCvcg2a)e(@Iv>qz`zwtsL0%cXwalfCN~! zYqLoEI%HKYa6n-~hpQ#``g`wFbg?3~;}`awi!YW-HVK|Kn~R8%O?srS(-W@5#ewyd z68JAQ4QMp#vYWfY+iI>TvM>f-jutw`05z*iT$-Ai41wjK_pvnwR`H-MCO_XewE7w< zD3OKSVq#+2g<)!hJ?GQ+?AgPC)#p2ih*f$^O~R}X>1hB;LoZt+y>qzqsbq3biJda~ z*vL=+rFNmQu(ArM>xg0!GFReif#h<$eq@S#04=r#>R(3!_cDv}G1S)9{u24h;y`FM zue~}UPqP>FP*c35BC=eM6z0n2E0xTU1uT%d;tkQR%AWS|i-*UO%+Y`ySUu{Y ztfSSf&GVm>!XLU2R;f!oJ@ZXc4i7a8r>2y={{=4SZ?j>($Cs% z=p-M4vSX;ieL6PQz{^ihv1?;U@sN>J=9ElL*)lPmU`Mqz@oE!9-A;Qb7fj<${=YoN zy`1K(N!~M@H>~!2N^e%~cd?(oKp@Db+jeVP+n3g0~W9 zJ^i^?I@;R520}?CzDu2uuelx@;7W~nt z`1=P#dhH()UFp_49vx(^Zo!<1$)F(x`l?dm@AYcc~@W-Q2?3YW6E0*&irH zAueN;Alj9;$c%%+)^P@L(n>2QmW?#2h^=9=irXIug-A|@TEyE+QBPzIB#y9Az;HJ_ z-TzH@)j~$pw)q+Xz4DVy>_BQF>1*J@+x_dzBCocSQ(oUCbh4>N5$>cG8$9XlFShrQ zC;qO?{t{t;c*{wf6mVme6gVLHAE+?i6we$8wVVqaP-nf&}szD0FiyLYy zFP=R+%79cexNtW=zfdxV5+zm%zFzAQ?s{>7rs6iT#2&`aMI8tDK$A`#f*T-rzmvm+ zzz9ET)bE>gEhijem?uicZ@J*iG8mM)T5~yfT@Uz|I7Y~8X;h;-h$r24THB&MvQ0FV zb^3LyTCf7G_$a-Vh9K%%sL^xHW%`ov1_k_oX%c>yPA)DkZn5vkHa46Rwe83XgjXyc zdL{q$dLEX_&fL*~=>IC1`9^R2xU@fh`hJ!)9L-5@Ce~$0c6X^EO#BJeC2cm_<8}&R zS5QBHiwyr3(fCJ!`;jd9`>@L=_bFp=mHX$91q#M?!YB>X@O==e!hX2FdwTlDK>?$u z#O;!a1ZL2^jEVZMLTm&3JI2AikXddJhJrK(Dma+s*uj~iL3D~Bs!b1B7qzmL78T5T*`O+T2a}(k zhf1$?f&4E*x&EY>Sm)l|x4qQ?&j7gtyLTM;5vYfOAHR@ObUy@2m_fYRo=~SoZ?z`M z0Go1`ijGy%JY008PdimH)UiC^bw4=VT!wTYg^jYv1`odtKm^p!p>Pgo9t}5N zoobzcQiW*Xj%Vm_)%FLr*(H`xdl4*Ocn2WL@_4M{eM|+V;!l($x}o4lj%4r8w>2n1 z1|=mW5Is!|;wezoT8C%$F@0}c|@G&20QQseq;Sx)GBeOl@(yA&J4`E|tjL7ygB)Ssci zbp)JX&{@EU$RdW%v2rtIWeUs>8|1n3s#$xX5(i~znbkHe3}t&zg^k~S4XL-mT@OKd zxRyJyp}ASzeitsP{vxkcgVyOj(u=&aApf>aiqHW>FJR`?t}-fv&7|Q zK^`9O#i~OpoRBJlG_PTv**&JcsMXtcHhNZC?EZpKrq9UCj*-Qn3GzUY2nq-!K#9!S z92^TM8M{!(B*uKGAmhz+p(+AJBd3M8RI*!>FX8Q+I zgOqvAWALc%JIlP>-fYI8Aqv%7d|zrxBIuE}R?DWd%PO}BnY`J>!$o+K{q^fZP!`pV zF+L`1&kTigsN^a>I^xxEx_a-Cv$sI)v-?8`Bn^IUte9iva+72cRDd?eq6VRC1*lMe zuOpsC4hr!AASD9fu?3U}WC$kHUOp;?njV6XAS_1#6QoPZfCl*Zh-JH7&vf5!r>=nn zv-^W9r<1%5Rm98ND*BL`7>4TQBarLQFBREfpJCUdAwdCkMbm*#J5X*9>}nEVP}3abdn)6%1(ina7&Hq=O(!^?z{?zv z^MqPmM~*25IS;qq?}KBcPW>E8&#$f)92R#rUvjC3_y!LY1F%P`DW-77WMHpgLB%0i zG1>~G1CS#LzDu=0{hSbCIt)|^RMX|Dp-?Y9l@#6tC&+wD$9i3`WHbyUGu3_e%}zQZ z6wV!C@euk4D)ZclzDsVM#+?v+L46#zqNR*__9#qs_P$+)*orI~=mj|_ zVK+isOAUyAneXNjRLW1e{S93LXw_Lk!Fk9A@o!HCG4h6$=xt1Nu0YnRv&>`Od`kp0 z{{>i0V81peAqJ77hV!OE_JJ~aj!A_?w9heVIIfU^x^Uq_dLxo~2csQ^;bsUFtlSum z&{9h`TMwVI%zzRkTxtd|F4AbYdRQ}$fB-8bQlhOA4BlEjgtG5Zr0lDmHd8bNNp_C6 zkc*FtJFK2IpXWRs>IDdym;5S*cFB~|q4KA|Q0{OFxxS+M! zUdlp9<}t|aKs_F|CY)Qna_By@07x0s6-?o!lbMg)db4B5bl!~s?821IZLtB*5kRvp zKKu=iu_K=W&K%ew3o$atBB#0|#9gr2fO=iVd39U z1c|Ri4o@MVRiG*7VXc#~kU9nq<@ex2f}*lJ5Vh6;@^1(`n5v%Gq6F7ec05aAXAZ!i zQTmb1i?jfo01pXhLirg^gV#5&0I{YbHrZFQ{0ny+eI4=a1xRlwV?IF`n8GY-ccL*) z4MOr!#Rs(*FkQ3oKr1ud`72YM#vc73i4gPygmGBJCZaGOD4L7FXduc4uoYE#>Kr_| z6jZZY{8t^r=<{zlN(vkIuG<=d7!(<-1;As2EFOJ}a6Ys2^M(P50FA>N#b@9Q3YzjJ z*w zsWJfv1{VI|VG$ll$!$=SQiQaZ5T(gL{)_F%Pv_F2s`^*{Uw<2Q^Dth)4wm1vfWMrkWo-(J?YI($o|JdUNP~03EAT zF;qW5kNXhPQ-O+m-eQj=SAfH$J$xn&SVH)U84yeWSTwg@Cg91dIZ*#_ZWQS}10?C@ z(%3p6fr9A8;1)J%V@Os&suR&L04jM(RRWFy>wE)fO@xdp!z1JgvsjAEX;5^8%p5gQ zgi`@Y+9JVHuC%JhR-!cMYEszS1(s$gfK81QeG2tV z^lKjMKO-nU>vNGZ2v!8ZOJDvC()%(x)Xdmd%;Tu72df>tp&#rr_%+rC+d-4xrIkn{ z;1w6b6NyQ4k5=jCWl*_M;i|PMsN4PCR=V&G+-h-80T~0~%L2US*!cavIY!r||J^O{;Ttn=wT5$!mgOsJ`g3Jbe{ zuXAxH>aizMsvL}Nhk`J^d{qp}Dn6rAWmiF3yw?Z^z`MTUQ8$N&qAbmB6C-!-vg@~; zUDiwHw%EaRz%yp1pFWw>T|hMITrJsiE?#3zsMIQlFFCM!PFr-bi*sLEsT!4gB{@}$ zywpMA@wo%pv*wMW{QIUupTt-e~BJb~0?7AK#8oMqgLn$L2>ylUP zLRU+NdfT~o`o;Z_!+M!dSLN3dql#|SCH+!Xa#uA*w6?A;VSS(vpvsQ0(>qY(Ji4><|?iLrq)# zkl)g$yL3|U9hh`u&(G>Mlo9}XxTXVSWO>V)r~K$7CLilx{HT{%>AM<9Ik|n)-JSRl zBbl$2+QjY;_W0JCq2qmxmeJT?=LC3T8B=5c8`TADSf5+`D(dVm&b`5!1gaKUX?ZNJzK? zEDMzPp%6Rg-8ICN_we0|cO~;pn+tb$vHP1-O%QLau?M&}Ha)^GEd{ekx=V~c&Nk>a zK=_-mW?4xJf{e&J93jJUpjSRqG0snfFtw^nO^rgv2zMh)BBR!w)o&#)J@r}8MCq_4 zk+gY9+B@!Wb0AQ?q{t}KEz8x;AyZkotht|+%HF$997esPE91{}8f{^do9rMx$S}UI zTxT-HQ(T-{F&d9~djTXioLl;k+}VnGe1UbV`#5ussSoLK*wyzuA4Y`vnB^vRsvw&?rdp@TlX-6K5RyMn5aE z`i?%&gW@ERRig(JY=}jxTjm7+CDH6!O?|OeJRGanQ?w6;w?Q8{pCeW@qEHB%_NU*(I~MDYxVOwO>W7JXNe1Q>QdxtryFyodA{91{zkY* zWQgo&naeFt--VLopH0TE2)%Z$TFNlJ|4lk7w(3-(@T3e13WL1+lJ_Rw>hhsblu3rc_ zcv^OZUIdh;ngt^VG*Dd~lyyhOn`4>#hpZCar<^sY**Q7$Eiya2B;Ap8y#J>Xmnd+j z%g8!t(8XRi(VG2f@Bo%P;;Q#hD+LviY5 zRaGFIx$FS9`r6v)EkgYPZlcF7#l7qeUOw(ZXV>G!`%BrP_v-e9Es3T*ySwNKm{vcq z85eQX+-JI22DVz%z8Qb*OOJ>x4u_L_U|~+2 zzi?(Lcm#M=gA%i<=f-qC7_yHsourf|V^5`DuW7ZcSDkeQ7}yHCsg}-E^?ukeFW8z0 z>(La5*TptHlKD?M-rXb7ReUKN7tmV>?Ir6j^=FLJuv)o;XnqKFpi@FctTz|!*J`P% zLQ!=;wfWq1Y5v9VFuhz7SjIEyb!d({=GU)NpYcqTwpeCS9QtLa?*cMKZ?5Cu;+&gfDL~JJnq`qFwKF-i1gP`4Z| zS9A5rOsP{>p#Sq3yN9_-(E>)+=eazt%I>dhs5!OUny$#Nw~$_LdSM2SK0!EO7V4a$=;0Ut>n!YiF*$Q>IkqUB0O0%23yv^1(* zjID(^ib&OF*Vhd5E%73}oAWZ;qKMV&zrIV024$nmC@7D$(z)d%I3MP*G1Pw}PakbE zaJrhc=nH96XYe}5l_PRBO-)VB&2;Q%fJ+Ke?@J!ZJ@AYCrAs>bm3v%)f$}O zx3|K$|blxeIjZJYIHwu!)cuTL!rxujPZOaUHfzaoz3G zc%44=Zc)8c8Q&a|i>3Jo4jdI~uaCWIp-zR;*4q_D&M>;4!7+O*2f`1lRoU(2Ly4HF zDtzL=dIMxubo>Hh#q4=_c$CpD_7D|{&lMkdKX=tJ?~c<3`!DhZxH~vnnbA)!&1`3f zy77^^I5}#NyN3GS&#$)kv-|7wb*$#T#~YP!td=ASl2^XS)7lS|Z-OR)b_LNa3iJi~ zpx1I_rA$rC3?dN&0)>D~DFBb1!DrZupRx)Z{(!SEEk8Z7x_jSYJNVn-#J%1i))foH zQLwJRi4v`jJs%Cf$A*{1m{1@4HxkaprG9Q3+ZndmD%s=0+l|rCheuY68_byD7CJr_VnaBYHe8a8 zotyda^8+R6PNao~Fa>#?3PHw{k`=u%A?;Q9q_WZ3L}EgIJU$ho3XmKdo)-g>Rgw-sc>IAG`S81cwpB1#ohcO zn9tPAf>moXeff+WZ;N^sXX|%yqKS&%V5#IebQ>g)VurFI^6L#2C~@t^CDRNf>UAoL z3p!T!*#lT{4^5xqg@ma@yP9YFy{i4*Xci>FPxbPe0D?4>^j;J+$S! z`0j9b=vNf*UO#c?0+1#%00jC(LM6}mu1`4hZ{X6u*X(}A|7*4Jk3jXmDfj%)I^1;u zw|9q6Aeb1Xq~hYU4Azi!<-$SltZ`ccxV?(s=!^mH=~qfzoSXo#COH60z{3&(RJT$R zhC;^HK+cl`3#o$&bF#LDgSN$^_Rh^C!BreslM3$(969An%3NXb@fE@2E(Fokc=xMZ zIOtkZGKW;^0MCx@dWWMla=N&8% zK##-qP$VMzhkl^x{x_7#U*nJ-<*!(vT~R>^bgf})Ab-b+g#?l`LkVOeIkC_N%*oIV zP7POBc=&<9%kdH-4AId3nPzwIS)0yOzX1b}#h0E<)Y5ZRj+ae$;R7gxK^zKo zoLEStDJP0R+C^zJSg}8z)9ai+5wqZ3QQbb6| zM(?y(fpogP03RQKP2E=_-F&;GCu|)PnsuGGs z2ti8y)a<{0BLZLO7Mw!ff4~PqGbJub$+8yvwS;Az?TP$O$=e>n29NEOb?-YJ|MtHB ziATBKiIAQStq@8e8tG~S|H+fx_V#Mhx;8#YUBJEb@EK@ciM;5Z`jw7?*63@=y=|e( zkw9*I)(S1K`s215_FBETlLkx!bie!^)bf{F`_*Fr0|dmQ5vIF2ZV`9hJXFQFmP5q( z!+u2t4y}~S-`QBSjWU5p{40~?++kpKfdlgY19vlnW*VpOwiPb7Ln%vX6nY+jPdNMK zwh~|stF zf-YY@$AL|AjfJpRfdhQH5Cxzni+X#?p}AHm33A|ACFpqMz(W0z1A)&%ztMr9&6SHv zpdXn7Y4N|nfv}q=Pn@7GvsDexSjprOelC!ZgTTq7P!!2|Ee&f5mWPbrnfRDSrp7Q`39nBQTXIy#)0n%F?Y=^!=k*@Ot(hOcu zM+OL{r7Z%63$QbYPs9O(1ci^;x57_Lha2YFzF~mNzR9eaFi;_~>2%Jz^r|=2g2Qo| zjD5m3&6Zj?p>j5cZ%EER9*nm(_s`jfm&(+D{^G!XwRuqX>sI;5U6A@O1YoH+>Nela zc`n|ae|OEl+ySnsk=?ectl4?y8^jKQg;ElLg=J~?zTtWh5&o3}{BtXmxVX8w8A@Qb z04EkoHGm`{+zW#E^}FxRYyt2-FSrLdH%??pE^t7{YLLRkP*!8>SX+WG)MqnV#0#gJ zqQ0FjjoHL2KBBQ^oNDSrG2FD~cDs33tpHYg{if*n|1s z_`ibGZ~R|DW0@L3St6bH;H(;Sm0^|m4hAb6SB=rOPO3~0%<(|YTjHMeq!e0w-J9WW z2J<@t_M^P~@kv+M|L_xlym$;VEntEMRtc!o^)Gt_0d2r4LDh%@i&Wehm_+ULznVxM z!=YoWp?W>7Z8wq&tvnv7Ly4ag3k7yX4j3|E$_24FuD|0QI4j7(%WqNAX{%ZuTc!O1 zXHC1ejf^Owb@}fK?r$lbKb#T-jta@X7H0Fmnj4_+wl5i11O3PNyzK_i#@2(Ceq2IO zrBO16qV03hS9mQb3t4lueacWTnDzaBmfC^;gU#}ijp*P1caruOwT?*IdlzY5iC*FV zF*E}{AGzH`UbGe5$iSIlH|=~M(tYXAS_|J*n^LZJy6L~uv;RNh{&J&E@bCmFK)38G zo9i|{n!SI^{auP@W{VRX0fm&A1A=xGdDZXm`X8J21E%Mv+Shd_binsNGp96gCw&0NV&rfJ$y& z@KvOG`Q6i|g=YSL^|TLt@1}XE0Mx{dvaio%X$pprYSRYj<3a z%Zp)0p5yyr|BE7E|F&5g0p?6U5t7^51^xuak=}Q(7VH5>7y?}hZ6_I`rM#Te?2JR( z7`-xZYl?)MNna7b%-4-in?F*$`JZ?H?qd9Nu++ZyxjO(F(6#`kdaiH{!kd&9h$B3P z(+>Q(?L5<`{*bP}_kjMN9kc?C4mw}-d~37l0~89{;cxeIg2zGi5?P{uH2dFo?jP0F z-%9>BZ4Z8q4o#KW>AN5bKpnoD6SQ2N?Og{rB^n^1X|O*CM1m)+@jB+;qdD{l@H#5i zxvcyqW!AY@MFU$vpn5{V`-w~&KxKxG6>cyz0~u2z<+TLRprLI5`5QY}lilUQa`+WwABiwERm)fmuU!NjJz3Ax+d3u0IWMEtd5*BnG2E>3-&uFpF zrJ1ynD(eTF;ch7DL*k<-aK{^m@YS7(cnW!&QjH;2&lU~JzNa1?=7dErjibc0B&K02Kfveg{?`hJ+n)#!A))Kq4SJDA4pqhJ9(Qtvy+`jKi!0 z!wL8DgTI7nLlS^sK(3>WR1RB&OyLmeG+OQ*B)bp2yO-YIU2-0UTKo0;)*~ycqi4KI zAz>(k0P}Fr{bdr?F@`{BmW*wH?sD*Y|B2|sgLZDQhqxJG+Yr(exIknok*0?;NG#Hj zgKT_G@o*5hCar3cvkk({#2m_24&wdc#sb*To-=vXPeQmb!$TlbwCtDT&Ys@HTBZZ2 z3KOadhXsfPJv;$89e{sU;o`tDQP0(W&8L-G zcmq>_)(U$ad=S?2)m1kbxnt_l?*`ON1wcsvriS+#0nkGQj28?gs_(4@0fUK4gYcI- zk#Q=}Ke~VcbrCP^vjzktkIYKt_8ep$s>u@=@&v+%jiv^xJmEW8c0QC^%65U%AK7-$8& zmkmrNs|{T}C}={2o?B3^V$tY*tWp~=!Gskiv;brqrXE^l#(D9fC%g?pytaL~3B!+I zVh)rU<0aRDbdmu6GJLA@%iFs!?xzOvdb=|KT7t23lg${iOV8X)-%-dE2H9nRd(CLu zU%CnhlI<60*qwvhh(-f|7HFpXwdP~%K9wAWhC8wQ!}O*i80`jlDuNO&9e4#Nq#s~U z;B8b$*K)HE7!&|#GQ!pZbuZ%3w*hbPYUL9$xk|`w1JGj?Wo3@g!keFmA4$&X=b2Rj zw|iv~4dCb?ggBfq!wbqrP4)GXfEw3-AK=AE_Ufg;d>!xgx%kU?puq~DU!cWs@qZoQ z)mUH83#9@3kAN~+HO52|C9z5{#OhFLoU`)MAT07L2(6%N)?yn5^B}Zm?r^q_A3f@} z^aW9!*!=XzbAN)BErPN?p`JgXSnBc}+y-3%%xB9Wg}hGx4u+dW_^yCx0l0i&iAspq z9Z)*Pg9lBVYmb41>FHD+^r&Lg1EqyWrmZ1NC6L7&#A!#~hb)o+jjSV6?I2rMwO#Ue z!@xEs)L%RyiD8uhV8VgC)?y%XWeKiAq!S?PF)*gemj223rcYo2;Rp(a-5`w#Vos`d z)?J18ubT^(n|lg!u&%OzgFZ}pioKtl(eZuLJ1n)H#~u5Xa~i7GaO9SjmsKrM)NU`H z7J$`J%7`EqLD_}f|M#6#tS~wX{#6t@d1?F@4BeUub-FJk%MwEz`;1f{#y&xT;4OvN zOfiCYhQr7r<$23m3Wm+VF_9d7EDvBDvdGHo!VuGf`WuAQFfe|Hp@S6ch9_|Nkuot` zCoP~nHYH6tWOVE8#^MMl8X0I(0s2}Lm!$S>ve>*|<`+F?@anV<`DAaj;t90hm4TJ0 zP6FVvU!Cuj1IeIWHBQ??7>%_{2g6uf?@K)dx84dzdUe5E59C_NavMe4+mIuS}G+t>$&RYnXPWF@;r zk0MP&7E$pIkOl{MUd2&_(%p=9AMlzWfdD_1_Hozg!N8erj22E^nb1BYXi^TR5LbTz z`t3LU52&3Wt%Hwe1Lc4zx)_iQSMaiuqg%mJ#1W}aZ_a~h?g!NhZKY3SNTt)+P>wT# z4#$GsUf};h4uSxiVx0sMo-8(znlXpS>0`T$gO*y1h@yvWr}}gFndL5;qi`s7Nhl&R zxercuC{o>fdKx%ILU;azMU=59L=l_I69M#f?+uj3?3zKs|(mv0*?4A6JXpfURSNPW|T% z3~lWGu&O5^IRLc{3lC_}cn2B8FVMGu^k2ddxKfXKG$Q1ddhGrnrJ%gW5yLi<-wfZ# zK&J%Y?l*}Wi?xaq4biX$iQgx&!FJYzDoud-f<(=8)I&gmNCK*khW3k4;J-k=VYXrm z_!}ZHb}a}~A<}*$R2tMM9$deX->Fy;-HL^L6fB;A0d5pfTebe;VenUMn7{ffJZm8L z%`in0L>kB^HuT02^llXaSVQ$ zLQJF_kSm6XlmexbFL2ESfIA`8DB(tM1s5(LBGK+QXaNA549z%*vjHvPA5kq8v7YG7HQbI zTB0|b2P*+ri>N5bfLDj%Y8uY7HV}Xy5G!OBs0+sPKfwE_gUiRuI}I{m0IzzUiscLl z8~CyiC&4Vr7VDp&I}@3rh87O)8G-G?0~-xnvI+P4!Gb_8`hSFb-B8D^MJvGu_$ z58$0YhOZHyu{@be`cAsOeUi)^VJ~0x1MJG{=RLJwtC#ZLNAH!DPS4oVn(pd;n3v{{ zJG7p`#cv%R7hgl45xbBit{B6Y zIUUh7Z|0aY_ApRVdcL9re8yL3j zG&EB*u=jbAtE{;Az9y+m%r-3xAZLk~!n4F>m!#=zHfNaR20;Sk&7nHpnK$w~djucZ z^>e72_-wYMgKdAxEDVBU+J56_(VGiZZ8-4c58*yA6#yBhN?9IWMmT@zPyagf%mqNp zuNFB`{^hA*!`zccRX_lG>6>i1uR$6aMCgRFs)ZdP7Z_nCq0xD zKC&2Rp@R3EZI22L+EahrGTG}Yft_H6ekwsu4owl2?7sacSnIS9*%`sQ0<*)EXK$oG zil;pSByR!?N=1{}Gup0Pt@Jej@*2n$59yhOhx!+Ri`2Jzlk^bvm`el@vJCfmT#b9fHcFU|U5692f)v zsX&_0kSYe5vD#I~R1qOs1++3_3_^k;0qPV43bX+PHW;lDs7SytMhN6SAK<-k?e86*_xG(%4sZ#6XbK(ev+gR}H=$3VecADfr%ylbcQ4g3T(G}sTcgwM zCwwJzl)NR27A+R~G>h{WZJB&9JFVl#akw^_ei`Z1KULX$OAFEhg=BW~$@jk5+SQYC zauug6Ejt`fS?&83axoi;N5jy?-xJ}cwnbN-QZ%RtB@RkviS`Yp>F0I`jw=mcDi}$j z1Gle7t2+v~tf+N|>L zC#UEDdeC9$fJ+`lt}-H8*59w+BIp^STv@ ze~@_eL%*_h+ZlklL;4e<*9V_VFzA#I9R)fq>*lJhyLL78W(R^4vE4f|^y$}Hd2V52 zKEt`Gxu)>^Jsqb5%C>etlW&)`DjQ1Q_3N+*0(mPZs`bbmOLs3>@2Lh({Mq?sFBGUf z;CUb!@Qx_;t8zTw)66$55AcCJ9ITk6KFSmWdozi6G^)>Id3evNmE3Yi-Kh6$)vQ`R z{9S3WYh-@TUd2M=d$zv^%lZCCJl%PtcWmU){bD;5|z_{7`-?m4cv)8Hlj#+s_ zTum(WPgxA1ygYtsWaK~%+<5zKxw0vDQOCYE`R31b(hGvQeL1r~{cwvdgli%sO{sy< zbG_IS<#zi@^8!yI{I@rX)y{x|tn;D3bL%e#!H^c9!HJO@Gu{_&#YN2A)C`Y>Gd(YE zI6LgQV;8h)4~{N)X92rWWTidy71TJ8?!RRm9}`nFa)TDau^SBgWzsB`^GBxpO1O)4 z^8y?6ZA043q8+@Xh~>WXT=)i6#P)de^4?f!>50#m9eBmpT*;Y!r+Bex^={;TRhr(6 zDbx2=t?ItIX{x6RihkUe^9vNJ8l0cw)^rZu3UFnZS?oQF=(T^a(;V;i%0T$^)LFK_ z)zitJk(CxCXGwUBLRDCmJgvVia^ULu~#A9cjnzi?4ZfSJTQv=&;9s?{)*3M zE!x%Q_j@dxTT6BJYkr*ZJ&X*iOfH@ssMeXoamt&HeQL0#Jr$8G5#8lSgdT2c%KX;$ z6aQzY^q)Uksvn!M>TR91%Cq$Fj3-YM>CB83cIhtP?k{6nK}cws7}Qm2$4pR1yWT9$&dz=^)ZaSPxN|5dsQ;ft zc=+^ebr@pt0{5w9rN@rU+9cPD*HF8WDpB+uR*`~BJ}YI&=)#{>UIlK@yIXm~#$e6U zVlBPsP=!}W)ILajr_qLU&p36J-Z}L+$;M1m?ilZ|hVetF`A~ans706VQce1v5(ndf z3#mU(x3`zw@M%8Y%&81h%^zwR^HS0&=vL6*@J>%v`=`JR_UU?5PQ53o!* zFyQe9e0{oF(;-D(=oogb6SPzH&mThEy%>1A3wiAFO}1?=Q8#Bd?BU{Nx>Y(msqmSs z{01O|Jtr10yKiI&iz3{sGJlY{6EG^v!HYp2;y?r2FJ9%OKm_XUblSVPs?4k2Pe-Tp9pti~ zjvn=3>-lP2_oFTTs! zZ#vBbi6iI3f2&8o<1eee#`g;T?9*6xC|j>=*?)=c{r&x^5U`f05QP6SDe}q-bU7+H zA{7cpDLgU_%v4~Kq?W}9ZNwNXi)ZKK<3ojDw7XhLDmQemZQF7_t>kd4T)C}wo=Hi- z(Xl1S5}61Ty6mYCF!>D6CpEL9L6l433Ei&0&yFBUG197J|3RxKq&X^{%Y7<|OmoLpYHfkx&Ss1S_oQfh$zs--5I z&z+_lo+_!9V%nHh8}UJBM8&y}=CL*SK1Z4sHQFq06GmN4S{PV8aV00T+ZZx3wgy${ z&9o{IiylUSK9Wcu#)RdL7roB%7YN@Ej={Ws)5ja9HXQ$^rQ40%5C$ITBrT>}qbw{^ z%`jNABfzE^ZP(2-4m>2K2IA);Jqi`oWF9##qF_JDQOidzzU^Mb2f~ErgvVVicOfjR zmIm$Hm$}lB)u_4JaWl@`2!~)~u9$|M?2CuKnW!L${DeC;YKSbxn?oP8bdNEOa($;rh>$B* z+%Uhb=VmRpnHLn=oquaELx_uj%Mz)8Fwze#Jlh}E6ZX~kCo=x^9^d&N`?OmRPsAw` zXcMLaBgXc$PIU1oHHNdNE!0Vd5gNW*1K5=b&*-yaw}ZiNC?Ej0}A%a`&BQ^k~tC_hGjtIjB)r|sloyX;N(v8maQ z5y_Ir*W*`0-sRCv9*qJ@_2?#7sMJdhs-V(f-Oma@ft0BrB13VW&{W|q?`sz4O8RO> zuCAW;tfi$xmi6kIi0&YN?*5)yB$Q3~}9m5p;xfXKN$Bh}TzIS$hvzxyNM$@Ka=krg8QIp(4>#P%cQz1uY7-ZHaB7_mC zNLPWak%xB*T#Cz1bh1XaFtKZcJ8^ssr3SHsm7E}EYv33%#gU;(q8zT4&Rq3GNBCGj zV(1?=4z-2UQZLCWHf(UNL(dIKa1pv)Oa;c6Yme8tMyu}{we24aXp^8jkndiygrh~* z_*nn>Fpc%3iLc3P?PUV!Pr(^*MFOSXVz<+DIr`Ni733$*0a_$m8)%&kxeO`<#X6ak z)ci#%h@3^Lk$c51X0fBeZAsxdf^Epg!3?m+$h~TTt5vQhD=IYiY&1f zhu)|L1h?zV)+%@4k06T`>KY56MBTArq9R}_5WH^{=bUZxUk4w%pHh>)k$(PhC9ApO z$alBi>SU3@e8bh9qy#K(x!iw#KxI8?B66#7OWBs_RuF6uJWzUMztTELT0Z6a6jU%% zdDvv|_DtD)HbG}F6^MYb#Hg}TYG_ns(&fwgHqRBIN5s|unz)gc1oDDlO)=`J4zx}* z6DT#*8O~W!8{)liUW6$-0$p#W0!FlJ;L))<4!e4=C)dVX^3xz)voStf{`+|9S*q1H~#AvL$~?jdF%e ziUMc?y@*hFWW!zGhC^2}cHa!&59}s6HO3tfFZ%GqjWjJ(C+9|Df||^zp@xW1_>Jhb~u6vX9> zw%dc#{`xJ16G~V8a}y((c#s6?_YzJc8$WptoWiky6I(%~!ab$%IFD|vWJ_?9wa2w+ z*xQp}YQ&~F(9B4@Q&n2Jkw*MrwbTYg2-bZV4L3pewnu&Z1BN)Eh$3ecQED7uf{Y;!$ykNls zrULW|yb_@BY@b&$)DL$jQW0E^!$|^%$`lV74Zob)IkWePBs?i*%NU+^eEjyu%CS+a zx|;5U6swGkZlA26j-(+_s_;p)o(cKm1aPZ*uh*Xe#w8yC0DRF=p4kfxA zBPKE_Mz;czhae;!4K*Ur$byOK#r;lBKT%e2+_|3xIOi^t0S_+=A~We}2zD8W2*-}! zLVFDTfVj`wRjNcZ%U2$m1?>eQ#<$;o(Hz?UH}+(oE-|lVm6Z;oOJ)QX8WjRHUY0ni z>s?*lRNjGKe==rk(3o_f8DY^z}>iy_Rj=YOZ1(BpuYBV=8 zxlM31A{C+!xRk6sUU8k-5Axmk`huReysWsoN^D|m1QK7FnsNraM(zy0E}h@K8pKyq z!xQ)WGP^q>r;J4!ueFWi{OWHs%A>i(!NT>(ky0=^fh9+~fVkZCR0w#x4~NT%LlOe{ zP4b>>_dy&(mF{9u-WVOK%*Ys#vr;(2=k8cSQG|Ag{&^c{@3Bf}tyT_u73+i_K6!H5 z(6Yc)ozm%JVfVyy)f&^C-&_h#ICZs$F@N5kx_E=p-z-#YcXC3I?dIktP*yk!y%m$3 z64$>ULsH9aWAO+BVBHOpTP62j+`l>3e0=!z8v*V3>lynLd;#HC3L2TQBu|g9Df%sSqf{w%7{w6#h-W;T!(3v~pZZ zR-X$Ktb@US;uRRa$=aXxV@#W=!0dov_K@;X0dfZ@#m>;9&`yQhMVE3LrH1Y(v(`}J zb@IaA!+$-tyn)zKY`=BuRx0HEnEDhFB{YjoNP=w%;X8TCDZCPeY9#P=Uupf{(OBZ` z4IhW%=q@LdfnKO$Q+Y(zRD1lc{AJijfv;1#@oV{KH{ZU1q@JK2BFcd?t%mcI@zK4( zF3@)6nQ^oP(?)FaRTqgR4os}2ibznWK9>|89Hmd#nG-KYjP1KimALDI9muE$_Ey>3 zbdexotTI9*ae_Wy1|y9}tRgIfJe)(RF(q<=9wnX2Nal3{`^mN>Nq7XbN42afOhm+z z(n~;W_KXTg#iaksYbX8?YE~1YW#PNo7mVi=`{Dgk-vh{Z`l{>;R2Hc=rI zA;?`MyCJ~0X$o5Rf{;+98OJgsp}Sg*$1&DQ{9_w4KDQYw5O5txaR%;~3K{YLh)@t| zBGqLX^q;nyv60q^Vg;p!GH`Tt){Vxb+>*ilzY%lzs z>%T<|eP`1~pBsPod5*6kjQ{j!DOWcC!N6Blosk0#Q~m{c>|~wnaMEqw{VtyCpF_XL n9_7TBeBu{1J}ZP^K|A%rH!jy#n#(T1Xy}v*R_}%X3Is z<5cp!Lw?%={Ladc9A!H>ZFzFKrIOTCZJnd!9B{-kDvHU3cl(<|x75;q->R_pV*T@0 z56)*Y%jD6cxw*u_+o%2vT_cB~Gr8kDnjPbo`#ogV8;giaQ@wc#@b##Bj}G-PA^%|7 zn~VJ4p=0J-{_WKTwMOJxzyI$0t9#F%ZG#{F_zHDVWGDRa`|rNLLftps4?q0dt6!^5 zB0v28yYH{){dAEZ{-;+=Qx+m=I2!c{tFU3I>tYvyx}q~ygQhOhaeLm`Tel5J$CC?&;H~g-RDHCYqu#?vt;{UFDaa>}NH&`_T@&94%r}-R`rP zI=hFnaLd;1lnHTy+uDE&n)K-bf1IoFhvyf~1yN@>b5gdpm$=N+)@anB#z;}tlRBqd z=K3+zrHC31M}N0%aC>Q|w{6?jmZaL!&=4)@G)2ur4|}uBm8}h`t4g0edp1wTU)v_= zGm3%Z+(HnJGMbVU*PCP30N2FGDfwlwhI@XXq`-G4O4-|b3N_$HjEyjKzpbUEm22Jv zpLTa5(n2Nj^lSAbmBjt;PlT-}+W*CIiT zsSDwc2oD#t>YOQ_&J?xZ@_h2CGUwxtG;JJ>JcM3u6!qE7x$*f`&?Bbax3?W@ZL{f6 zvYRK(m1EA?cc$z1^z?|DHy$|edS?tnY>q8)T^t#rQP=kMwZutFNlAH3wWqLRJnd&v zn6wR?G(E{Q>O4)T#bK^wzP#!QE8NoC9NRC`UGsm?ak(xxiL(mnpL3jOTpX>@?(hBl z@s&m6Gkk2UH2f&HMW%#ez;g83v(skv{Y8#w%EEiyT=M~k2El^|55A~SrEs}^`lDdb zT|i2T9HwpPYc1M3jOSBmekNivT=7s9z4^tyhEqFaRQc*v^1wWHZO~)>NMmyG-AM<^ zVvP?|qH27(c~g|z@`M*@*oVh^58vg(M~+06pg!EHP27D$Y@aOYV-WTWJWiYLH$1|^ z^I5n}tCQXNHhQ_cQL-U|<^~?Kc`gG^+Cf~>t*x!@Yja-2SeMQOueG^>w$|1_skuUr zxuPVM-J{8`0^LV^`J2y;`a93|nwgrKnwc>iIQ5)@36fszCeITHF7t!8Z{OC{)g_Rs zgtzbJI6zLi{_1kRLC7rIQs?L9WRdcpWe|a9BW;Tuu79d1b#;y9G#ao*`F+stM zs}Vw8i!Zj6@%vD#9m8r9yqy@gfbG$|yn&l<0 zuSRHjetdc9iI73@Gq~$>aMy-n`>s=GKE}jwurQ)z+fJJ2S@$?Bjn(mZuS|Dg?tZ-V z>T3NPHpPWj|3fycXZ1xf0+58S4 z!XJCLHJ(NxGQsz-+_-TAHY=9&o;xuyabjX(5X^u--g7BIDFn<~TUT2O_CgJ(%Pwvi zO03JE@9PI;9&2#djt&m6?q%>o1}uBA&$=tqV5TaNQ_5I)b*6{K10NZAd+wJDe21bX zlY2Bs1l9YrVMg46hIB*M;cbqLD7MV4?z=z##vk(`T{oAhBJ{YrR=Um}SywO_xcIU9 zUdDr`6G}H>!y3=h7JSegBqQs=Qi;_OF!(swpJ2sgxU+EDG`uAayD`aBvD|{D#m;;3 z3kal94o=n8)g{F`_xke3b>~{N=$rzF0IMnOvFhIu%;hnYt!?Ob#Cu`bhr1d(9`T{| z^;KeHB#W%;ToLgRBR7}qXV@7@DzT|DgD+pcw8N46!s4QFGOy3BTGaVnxq7w90CnqE zQ+2wt(I;HsPpCCGio)_llcS>}Z3RO+D=yyEzxCdSGf#A9`wAFoo;l_-Rhb4Qskeq+ z$oX_X5iTe3t{Rh9XPq1lWD%$v@B`ODH=@&Y=WHje^kOwTVXbVwjIz{_jWZx%z1$iG zN?d$!GRJrX1!v)o9!p20zT7W(^X^=~#HwVtmX<(%kJ|7=%Gzzx&Xs<>O25eSZcEDI1AAq4a!l(GqmpSyUAT20Eqao+uIFah0SkGecfP+!boHf4 zO<*?bK(SNWaCs_r%rCS-^bD zh0$*YLT^MNEoNWoMAk6C&QtElPLwQ*uz7|~Ht(uOfl|m(r}@FnTE3vt;YaMLw+No= zOFCpEm}IYt54xHp_Y8ejk(^8*Vfr5KX!xw}wbCKwGFM8Yk}X=|IAv=a8?ULV25nA% zdiS6vkh2dghTXQ1C)}?o9*y?CGlo}q#I$3_4)d0{&B>(r-RxrNo)32JKY8rfF?t?- z$ELH$e*5^cYGL?2ze0=8hbh32(qr+DBF$7=EXF?VF?hhIk+o(A7< z55B+M`h>LfW`3XTP99SW3nU5%pFDYH9Chf-V*J5Oy@I(aR?K-CWf}2SI<4v}0gKa} z8PO7s6Wdtz?=*_kRQ5*D&gP`2pB&b6aBzTa6%rZgHV!Ts{)e_CObcE}c^w%USulhp zBP+_~wmu$VvaU}5A|YXnN}gxjNBxz}qrC#QGqdb12r)A}&-cYE7Q^~N@Lm8%daEt5 z=~a*n+s@9_KlXNT*;#MKGF8A0u+iFEb8>RXu-Axanj6F|^X8a?J8gU4P8JY0GazUN zJg}a6ZmzybUJDv%J!@L~R7cwc9KWG<+J#?GFN_8vzj^sHPE+j$CC=kSVoR*l8hl)4 z@g@Xl@G0$J(L^GVurZYe0mSZPIk;-?S#a?la4%WhjFR-(pRNnPo<{SJDx*5-vet~YMn~y!lN#j1h?l|1-&mMTV<84PDI=7=D7y( z9NfQC`DT$L(Rr$Ua=>M%Y$D2P>ir$=nbB>iD7K!xC?-nd!t>(E#AsNS<<&krR#sLB zcNnila;0}^qM_oGh3mbB9bI5qQu2+b!)c?UPjJjuiMQj_&`@Lr4RO{=8vAPg} z%MOl?h_k5Tn$>05x_#HJ3!@&NYIdRyMF%TyK@H#8mpjoCKa_?(9PuKEBa<-;lY| zX*y~5FL$@mq1axwKgj-=lhZnMej|-d>;Sg@HY9c2LX_vdO`WA?a@T1=$%_FJs56Uc zvx{;nCyxtJ=i_N_6B2Z6A#X*C_A}1*xhN_tXY_?LT-rpxtQMa@eW=XJhix(4mDNYM z65fx3JNwj}E@smk=R)l&cACz2YfC(9rRB3@?IoJ>VY8^Hh*Q#;14n(Rz~wgfh=IK` zQ+EJXB~tjj-ry%IRs)AGA8Wjsn%P{MaWo1)f6TeN-E|m9erU%$hGPf>4SNNatm0zh zhPQ9O*jvQ+i3+3@7Mj*2#W#AbgarqWfS>NayPSb0PX;v%<$QXu7Vee*)@^yeMSv9X znPJV*BS)IzRw`PL#6MyRoa*#yd?u=AZZ1SM{Wzm5+V}Rh)Z~<%^imp~-;-BG1_@^L zMhoy9B%I{av@#&%F3)_iSe+YSmwMTgYaS@=*dLum$p8HvIV|8>M&Pl^fnkh2YsSqy z89U0hf#S99a<9H8!siR@M<3O1XxUgWmy5K%r%)&-*c9@$dfc+yce)HZt>ua)21txH zh70OpLpP1AcUQ)jd(-VkF@ZGg^Z#=BGO7 z>xzZFDcI+@6?N~!AwOLuC8Y?AgneDo6Pnmc7N?X`@mOnvcBamr+oy(u8_;^iV_B-3 zDRMr65&F)T65hqrzW6K>F@ltAJ z;VquSMf(M#aI+P^@m&sd-^|k%m?oM`H#751Jx?nbkHBC*7CA;7Y>V$HGO6*tDuiB7OKS9TQb`{dso@|m z$T9BSJEkpln(QTyH}K5ATlbcryjickVdzMa)78E0Ib_)y$s|i*7g&AMTb(C4 zuW4=c;rS@-OHGYleq-rW^w7=XyLG*1)A*9Ia<;A+ZGX<=N9euG9Mq9UNZX#Y(CFjK86675kH)5f+}mx{ni3M6QqH~LyB@63R4er6_ypzXaX7ct zY6kYDe5(Sp<%!nT&#_V^F0&Mig0Vz#8S4hU>4@;eyelWUcqj z8qR9JX$}+~C6?s~&h(U}dA^0RWJDEXMmP6<*M(6grSqv}3!2U^vg-EbYH8zOFF>8` zw(bV|P>9x&Z_`&TUTfWv>bHGQPtQ(P!FrjMxA+Ks_bNNw<{h&p2=fE^1|>|v#^b?a zHhopt!#X-T&!VEjqoUY6<}}UC&FPJX2Fq;v^3vY(Ob@;yc8j)PZ?85AMvJdC)=zb1 z;ga86F=0?he5A0KS@73`9{3V*m(Ak5AoOaVC0Q$^S;Jn9{IRfi*>HKF>f6o^(9B~k zuNV5#Q7$q(e$EIY3=>;Z%dW0;YL1!t#0MjcvTDocj_K~KFVaG-zt2W>z@4y3&ZJFF zPSzZ@WRk6IX?Y?|f1L74M?D{0i+9>sXn}sNxiY=cmuI3;XRQX|p0iC_ZWexez$9yL zzXR7YA!NQgqv!M;+rSa>VU|(UKI#IR_b?(<2r$zPV*P^dpxdK&=l*8DhN%Mi|xVUVGswJph zg)vlJ`r)^G_|UGjfzr}aC#x=h4hi=!&$16r9>jQ-pE!Q}xW%~N()NcBA9}3LNH|p{ zw_Wz{&9|BE7%DbzpR~9;)$Z2$M0jQ}Pe9%dv!T$ijk}cK+0#1e6XQCeYq2I}`)BZx zCqrHq*qOLQSh;`$67KDjuMmU1d9xV1SuCh0MjT5&;83At5qEa(nMvU0SjeRk=h>4( z9`r9<;;5M>NH$ zXqH#?%jLJ*5-n>^iS=4(;SNf8NjX~wq3^_-!n5_$?i)^uVJq63X}D?D@zsKUQ-8=* zAyO77C?sT9w$V^8^m(zTJwHQlRJ6daXbbAk7okYDB$uC?WpKu5^bx~+nG}I*cjaPD z{L`M)4n4baTa_~@%ex`>@J?J7eIIpIwdDX z)bsK9j;6NXOw}Q!I>AwItVKV(Qj{BmLOoZK@MirL&e92^%{&aHrt zkLUKi7Io%0vm3Ms<(#G~Dziq_S)l$7kcR|ozwg-C*i4O|sL#?a=gHRBy?dT;cN|ww zl;>@VI@=fY>e$JX{_@bDVoKGsvtM5t$16fz7m3oy z4g4YXKQ~i#nwaQ)u$SUUwNM1|;Vt65#F%gQ<=Y&-p{2Fow{&y04_^xnE?)d}7D0yj zAei~#;D`uTUuY`H^CG1i{7szOQH_F5Rf@Y1nIC&*qB)k`SKoE`A-)zWtQ)i8qg_K1 zt`?`TXJ3+#8-hwif!89|N$cu64f+-gH@>xV<9U%M`9XD5b8 zosFfOi1XjsABi-1|JJ(w*5v)Gb^Ffp(Vs?7!^8Kd>aCeV3kk0Xg+r>Bb}3)b`P0lr zv-y3zc&J6hnBAZrpB__$+@a#dO&mYAVUce?Iu%NDgJj#+FG%(>p%$m8g(C`1^`XZ* zF0ZygC$GfPLPI(57u1wR;$A|kL0esHlmqt2=>1R4Uz^828vWm1X^H;2y9|uevd?;` zT$=f~pkRPJSa|Ay3Z*39x`#PcPbv}Gj4xINCPPC*<$0^Bs(h?9Miuy|-J=RlwC3g) z=O3IsNNjI!x9l47jfjim@*N`*wWai?rjDVW%jjEJ1*6Y&_AC7#T+h@1QPegsbZl(% z_=OsR0Jh=MrAsf0J8oF!@Tz*LhnQs1EZ!U)(UwWAyQ#QnwY{pvTh47SN{u@+S6RX( z7*kjHXY99v=v%Y?#~c2C*}7-YOyxmMy%>J)qrhudx_J5UY0LIwTeqRqe0tiUOf~s@ z@5dGSv4Q{HDbXV<@@iycL^0v*+q>v|7Fs4ruuB|*HeL}tOPCKOx! zzEqwG6*}SQV2p$i{oGdp;y+3bzn_(Ro(X+j&AvqNy@=0xi!y-j`K>9M+XA0 zR^L(R;xkK(`it`7x9!|7&kK!dd~GeBj}H&+WCdQ;^NaG|JViLT{8SsqA+NPdylsif zPvjxV;lsm1ZgzVZ8V(wrUxgO5;uy4FGxE{#*p1t-Vth~^)H2n4#Bs@K{V8wWoYE+0 zHc;MRK`$){9Ay2~<*Uf_^LX@KR(cb~!?b(&1V1D;ctsTzm2cq)*7p)GG!-7pL)Z49 z+JL*bliBAFFYvXXmazJQER)nbTbXr_B}+TYu;cRX@$8y;tM=sUU!Nn}`1xP&J#u*H zN7yeO*nb>bX&J0smu0HY^n;K4xpW0S4n-$~H4BFlM*zfN1$B(`T2K#Bj53_s(Yw*ml%>5ECbod_2Ly^S$?fDbq zaoEF454b6Zn2#Sjmh6l;3%ugx%a;#3%$LDcKnQvw50RM{4=vW&nn88-S3|c!PVtq` zEM2Ns9D~MfhI50H(~MPTy083KdwUCwkUfp>A^ZL6>@(FCyG9;8ld2*zF7j!GP{ArL z^TKvS)~HYv5%T`>@jrU}zv6Pou0QTP)jH)Hw2SLJgj*4l%ZFQ`ouQZj9YSU|(0ust zpaNXteec7FR#borX~>e?23dmIz!k|wfnTcxkgy6anHLW!ohG|PY0cod2_Dp$Pyr1} zULkD%#Z_eg|1-CjZcfi34<`Jgc*X3fY8;2}mzf?%wASm%kFOLCJM`HUT;dh)zaw`z z7?0nRs@HH-TDq*4S~)LJj$WLXoOlL-78>1WnTwzW$BWlY(|S17>3mH|DMjoC3*CG- z`!pEOhTFpvCr()YTBKfDT%S1de_%X+QhhPojv{q0A0Ad%@$m8EDPXAttA=pF7sXDV zBLy4;Ue)Ax?iUuxsdd-y&JRj67eUwkTa0hVZpOl6vg9KtWd@dIH-Ga`BrygN7eAMU zoa5_Z{yXpgvy|i9it1^yq1!@9Egjjo_QM`zxel-q4iiXI4^G6^G!Qa~$<;m;aw6hraopT$Cqh27iU&(x=@X$Xr z{0BT(r)byN-n=LB!66}O23zkM#X}zWVd*=o|X*54OwmkBZWNuEont=s%!`lJFPQxii7V z&a*~)kw^5O7wZ2G#P}!AWa_hZ~lPOkRTrX7Y{BawT zNpke)^*Yr!_o-%h#@4si&+j(}*}K zu|b$H)01mqZ*Q-n5?Hho^#V_AR1?$mfm`r~+BB3#gg6dWwk95QGMJqTnpHGjegPcR^cK$gq^OMxc3Bt%5Gy z+}w<)*&qaRc$}!z)Hc{RL?zJ31d}3d`r&f&oE|VYA701_OkXBoHxVvFe8|%gC=5}H zW)3vgvnnHiOWM8K{~$t-STvs}&*U}?oi(aJuDB5txA%-_xPnf>g8mzes?M->@CkH- z?$a6F^JLyW|GTarTY^TWQ+Q{%O%?6cZ(q|f<_g_RC`!O3rP@SEKC*2*IyyarIQN}1rcsR z1wzC{dZWf?BET47)*03jTLbZLU5qaf^h3}=Pjwao-bA2`3nL5l^vmb%2)HU9x|!G1=XTg zX8XifjiJLaE{w?2h+;q4YWDsS!%=>eceRU!7UU!RkUYui!0KLNH8^A0o(#1?d%|XQ z06RY@%kX+;X68gMqVOV>`^-v4!Js5S9E>ugLq-kYPAeUxrTYKc@G|M&{ET~$|YA-X{&ZLF@yoxx;^G@s}hd794|)?D3GF(QeAB=2?!17 zG)F~8do&0Z#CR^%g&bABSzv2mW-!~H(#Gl1F9M(ol6Hje2GOacceDv4S;&szMf9n) zDKJ#XlI7?jm8OywM}y9dJceA^0MvMwetVz)R*f~Segca){Bq$|XjMA6j2B=sJctFS z>tq{4i-r+RxwCK9XV<|SgIL)69r;;BAo$i8_Is@jXzSZwg8ju!UGQ1L89<)_%(x6x zT;vj1!}YM%~?AjWa@rv%UVJQ%=H&7zkDJjx%s6H zO(l&oRhUH>?5B}#fXd& zH*@JE-3}fkA_LpB(uKh-R9Ct+TlEAy0d|C*+tWTSL|8!=RN_g_Jpn3O^5dneMi38bvg$g|^&j;32(Gys zk$XDsU^LN>azXWkty?-4e3kko2|YeOUS(?~jJtZWX9o|M-T%agfEe@$Cv(d9J0S7~ zLOMhb9Z8+I)`v7b;x8z+Gd=AWn-Wu30UZGX#!rBy$?E1bOp519}hf` z2K66qfgA7ur0Cc;8zGG0vZ6tp678fy*xGfL%s!yRAG;Sv=bHS+zqvyjQwCWX5Mnr3 zULNnp#zqh$x(U0v&bz_`6x7Q{I1}TPZU_!?^dJkH(!Y5A{C9XSl-rYUjNaFa7ykb4 zzRtVPQC67;MU3U2>=y)TNXCAo@@9Z)UX9+$h7>qOAOddE4@19!>&z`RVERm&%Av~u z{usf+{(9Dc&s4?j6a$}~o}LbvU3Hu(t@WyKWF*9pY0F2jp*+A20E?lR1WAj9f6?$V zp@72!Aj9q5HdYSDMv?5*aa_Pj0fDQe@R*nwSbbA-bEMgba3Tas1L3fz9|q>x`iRP9XFOiPM$KS5c3$3$C?r`;a;5^v83fokP1it*b(yP?>GDa*Go15P~l7^oglXxmfNS<1bj5P{(CIh)sI?6V3PjsZNJr0ZZ1GW$)FM| zSqAJ)m%gL2*MYeQUZtoD>U@xR-j6@XrGW%tL{o#cgq9Fg+c=`s&~H4RU?kZd?N-K% zBUOvEA`~HOafLqr9?5y*pddcWVvFAN0>#TD_9wEWO{1{pmNUfRYj?#JD z0T?ke=GN7&|kZLg{CMMXR#RgS8iXpwebZInF8nT$RmwT&)V^$Hes zEA$4#MH>e6_*;0?jI7u@ZwfG4S{u;{IH$)!b0i-A@V%_GwHDmX8#nY-xBrSt*zfbF zk5%m60 z?&afHg8^L3K-_Nd47rLG>OdWu>J}6<*v`6t<4|l0`?7ArW`k#EX;>#Y!?_)ma_K=n zcag$5jo}gh90|v_`i0L6NjR2R_(GB4)&>QC4liIS;8PCDDi+pA z%@=!&RxZ1Lx~WJ)d=SfmMVe12b1QL%Jg5O;k=w-%({ zcU?k~#L~t0DqMr$b%Y6|m+9$eobQ!5&y~*j`SM+kE4OHwYt+gpx9{kHS6s3>CFUzt zEJEy{pz11ci#oJ_79VoW@CyhHYW*zQoZdZ5-MYOxXzDiKWkkK4*?V`kcXnA*B70fq zJ&VC5@qF6>hn{f^xAcVnk_6L;t#4LGPtefs6-r?XCsBW5AM0-rBq^Z)?;1GwbRn`e zxMaXmw4Fu#^ygCx%lS(eVksT!Drm1pes19%JTbJqCm_7#Y%~Z1{WPaDa(!jH-rHOD z)3Ywk9^|GaIncbi^PQ|Z<}KS2XI(N&?1%3&LV@4l!Br=H!uk0l)^VC1YhOzYkCCz3 zN1#>^rCKGn9()G%?H}DA-xhY#$)gcL0VnyDZ%Ld zhGovN@QNcwySvYndpx*(NK2NUqV(~v8p?KC~D zh()P2rV4E(S0_jpc_h+iC&wFPtOin75|!CaB8q{IRno>;b)?4J#zo(J8jo8oA_{^} zWvYjQXQ_JHamaPHV>EDjs8~6=1&!HPA{ibYDzidFXXq}QRXrKEhh=a`r*6_6Q*LM zWq0PGiI5<)Wk>2t4L9yfL=siw#tmjdO+vy#6K;LAeqTe-gV4^2*bk?!vi4UJ!JI?ExQ8{4P+2u0@~--lhDu+(ay#%UH7|Nm~&kay$!Q| zWr#~UCtR4E{)oSL$~Ga%;u^WHEnd*z^xVv{RuQGor%y|-tWe_)Crt`;f^T}4# z2FD=3qKi$!^-jT}hp+8oHiZOF`=ogoB$}#_-0a6K+0(yJPioHSd)xB{yZIY`9H=B; z7GKUNj!r;TGyTz|$KqMF`ADuy|x_GBo~zpU3&-0Z^mTYp=wt8X=^V?@*v zt4yn|S2y!)H(S#qA)*fy+P{CfLwow?1d%rrmAW(&z%B-2ps*pup1jIg-zTiIfW3GT zTNQxIzk9nYQvrhQdG9;=v@K7lb|=(s-i*TIKc3Dt%k$X9(+InZQ!0PHuVAf1n>JX| zqHX*x>Pte7nC8slPspk_ep~#}ev%S9A?=2Q*~4ZoZGGJh3Pzk+bHi3T1|d864A^MP za0(&>;xFSMPF!5vr75aIufRHh*s(-^qUo1&YsrVli)oj{#agqYsc-!C04W}_b#(w1$K#AfK9T3%j* zA%Q2r#BGjDnpDTxPZWZ`6`^Wl&t5xVNzH_^M8=gyYo`#s^U98Oov=tY`bC{(pk(cU zh~*c+6Zeu^pfa2dRaY$mpI;LvF&EH9e8j}NuGCyVuB)ry!{pBCG2^q7^{#ZP9=(#Z z2baMjHoJbQj!k7dvR+$1rF{S%2_O}Px!Azl1I5a)1jyyTsMxv(@~HlKfB@-s&wY4d zGA)*Y#;g!7ju3$@w;-KtlwsGsQ6?CQRT2b@~y*G_m|NF{gpwwlCjl zHJue9yn49g=mFV&d}(!J&ZhGupLYUNy=7}ieXoYSKkWK!ufF^xMMFbFbMv(N?Wd^6 zrp8XbwH+`g=i7|q5&Lj&yIu_ZnIQV(~y z_^st26W@uDvoX7Zxlwy_Q*-R*2lJsXOG)Z4E1K@g@SPkqE7!Q+5Q)2khPFN){i$N(Bva&Xt>n$A0xr>ZU?)f8$fun$I_HkQ*WtPiL6TIyQFE+$PG=7MIuEtlgYaK%(12$cfWcaG7;)-`Uc1%%1I}7)IO`fnzXOFvUi49);w!di2n%PFGyeF+$IVyaeL{!kJIQpibiKaPw#7p zDl}h-5EO-DUSvym_sdy^HQ3kl_AfTfp3&Bhov_6bS}GuMm!*GI6Y~-HVtB%VoF1JO z+wu%&OD5Ql&)?biiVe5J()A@HMQ3$>Mq4Cc%X3>zuSxX6lj{{bG0>}YBjRkDhcHp( zshH*Fl0ysU>%oMXBGafkS(b?#S^XV(Jmy3XAL1I^S zN(!8eQg8uNdSA`3INzjOG*bEEJ>;kCvLsK<=I0k{?1u6)MyABW#=qMms9vdRH*QcM zz}{Vz7_}>2@9dW@5^na)G!7Sqb8#}{{zc%`GLw@@xh>(PqzcD)V4Z3A{tzF$+W>}& z-n?r+Hck(3RR#zk0x`)M0$d4&&hk zZPlbF=s5KJEE4E%=tf`SHp5`Y#mhbVVhQdG5sMxtPoAXoiQ=S*F^d-d(vDS2;|M>l z9G%bO4T^?=`+65{`EypPj9=~Q+WbjST`4Q^c<3?&<*=56H_wwUIygTDNea*b;4$xPNt%F>&|XXRi)rp0t5O!pHuu@uUYH!u9+560;3 zT^2?f-z!BpVgRid)^5!^y*hmQ{; z#>d9R$9K_h-&iLmM%yS4%Wp+F@IbR`JpLeZYV|^Lh*a*z7tY^*-E!~ft#u2hNcpTPD@)MH3b z6rulgn0XQ^_QN^gULLf^H^)jT@P5^%Kz!ckdzhbJ+hgyd4Mf*8&H%_{6~|&6n|AgVFDS~4CARbKq{SC=pGLfbwN0S9)sdIs8w8dpA69D`QlV+2tc zOJjao#_>zeAycyzn&XKQzJDffzTio%a~CgjU-6S4#L+et6X4JxU5Zabgm94jqg@A? zef6Qd$JYYg$cqQh1sn(#3+f_ty%fiwQOMYlp~6hqP&LVe_OhZW^nwwFqZymt(Ryye z(bCc~ZLmnkP_gC-F+ zF#H4a^>YnPOVs-VlQ!);k>Ix4W5I_9x5kwt17|=^GrK{%i4PCwedT$fo9T705$QAFLu5)3PWa zS=ACxlIR4WL2)cSx7kk~8a1h2UC_5>_DzQaL%l896bq&ctGqjdx7F2qs+hU_!-xM4 zTBtiaGVmg9OJj-&pyqUx3vg7dTnvPElHwQ~-+CgSS+?4%h-f}pAylxcU(;4Kgs^8u zh^iEh5;%_}>tuDJs-^hRxp=G7#*2>D|4P)MF+MJyD7Kn{vXk_>x+a@3^>aVsD&M)B zf5%mlY0yPWwaEozKoG7o%t}|TNSb;D4rzPhz7ciTUwW;Q(EQc^lHeOZL1rKOqcT3WJJbJb}zbWh991loBZYn>S4RQpda=oZRR+SV*! zY-~fbF<@TR6Jb-~)}7l;Ik|T?ty|VF7XeNe1h#x*ae7A;PKd3BggO&zCyIPg^+ji& z{{&5Zm*Iajv>#vnO%VOZN9g}F3bmxa=YET{FpRHdw=3P_>(6%f1T#QPF1t7Z0RAWZ z5b8dt?%rzp`W(IxK|0g8s@M3-Ta*>t-Q5od1Kwp5o#D?bMBx1z&%1Bv;(vO8YFUS!W_wKtL;Xqf_qvG9ryp}T zFAC^ZDj)}?eL*tgV@7D6yuK8H#k8?>gAy_3gW|UOhnIRR;yJ5fyDn`UTAB4kL@s9J z=pi2-O1MkBklxD7`;0HEj5=;SbBw^{7pjOIi4HD1^X|JL;KSBs#!i{5tH3CRe}K=O z`2||(9r<<5&4KdBQEx@lclWpXq_-fz#ZUM-Cf|hDuP^Zez15NbjV=OB;VTG%S2Qv* zI;UY3)jn2ed76c7WW647L&(GcOY$(RX2H(>4O z+w>beg92cUY4@hzfq%v4oNC>>!LKTsg4~9$MH=g=dSQu~yErA!FiXsrF7x8yq$fwU z=mEk=Jsg1LImX-{tEe|9YwyjTb_wS#3I zeE6>+4=PGa-cc4@!h`J$e)7;H0wP$P4)t`~S>>G5-yzo@E#=2oOh9{u`C+mSW0gbx zo1Fvs$>XVeA>>$!2_T{3YeAyn_N9EHLqA4*GcO*FBP%8(Cm;Lf?vdxLXbR=dH^8tJ zLXVvDb}I*cSYEmmG{Okgg5Ji9NALsYBGA0jW*rS;v!J-wkgC}|xW4rLanQa!j-14& zU`Un!3DNz27s%G-=oPZoK8j_8huB`0uc3vv-oPn7x*}M21-mk`^p*y$RqlS zqG?B}rcb&Lw0XWFMtS(CTaSqiM9x18B2Ollr_g#r({~N|{6jzFEot!X@DJ47NWY??7wkz=@z&%GlS zV!lH@Ia1%bx&Me#1FApenwfOj!o$H~^TfnAM{D6ZqRr=rkxD!&us^b;8Ujms(-d0w zsdYVZ@2LN`!16zR>Y>u*W+}X)yL++G7SvPaE#LBkzXjg!JNifZ?#EYunbmg|@7rXR zpT@7BN31_Ie%hUf+bIBn4hrRR&wnJLUe2pZe0XvC(z+7bL3A zvHUY1pr*Gd&{c^j?GRI9^k=8U=u|1?BV_0+)gZevxmU3i^b*Uy%K7Pqz@YX1Hxs#w zTaegQL2Rdp{1v(|52n^?#A@~wvPQVoY3x~sBG_i!IPcjx!ROCWq+ z<^}01%UR0lHIj68Ti`ZBUgFxz+)&T zfN*{^8m@}IbLclM(aQh2l;sH zZf^w6dwko=7PS#a8Q;gcQA_kigQ6>v&8+oms{8)6s) zXqAK}WRlRW56|Ie0c>ZdP%bX5O8f=@7J)Wu)sr(0XPG7%BbPxK9iNz}rse@Zf}w46 z0h-neQ((aKg>SwEwVCT?o~7pI!7m`HJ>1FqhzZu=JnRC=A?zmi7SshX&G_6D2OwFh zKyk-MN3$PX20o|%ZksZMD-|UrU0}cfNofc|Q@hV>zbD7H@~5~x5FCRrkOK?? zFKLeDv$^blKm;zgcLn1C`{s01S| z;w0{V{s7ghD+uE&rXP0h*a4=8ZI&Qji=lDzkiJ3I;OB zc&iysJb`Wg2_bLbzPfR6+7}tBL|Cd9&`#GeI=%HmG7T*{)0Wf%^ABP}L(kx-l;2p6 zBG?fCMlt8Rxc_0BUq9zk8; z5S;}an1XZzj+r54+ck3(J;TdqEB_n9Ej%PdJ-Z18;lKzY&h;l+l`ttN7sfKdxFsC$ zaSS}5^GL(+8yJm+1+)+n1D4wqG7N2kQ{{uuBmhYeQR#KwfbUx!Q9y5egas=wdjAM| zPXy{pR`%}zH(5EH17q?)usi?(z&x5E2ZC8BJl_9}3U2<>`Ay@vl$|`g4xEBd<8p;d z%E`^e00>emW@1xMbNn~fFU-z#q|*0C3Xwexkj4|k*EwndK=yfNoyukYGrq6VPagm{ z49v=cdRzu3NHJBwal`ugjIAhHFCp`Q10uZM3y`58gK=tLPLMdPexDsd8fGe$UWAEO zc7?-_K)Ij-vXo{l1+zK038axHOcls{G3X$~Id$s7W7)|=0j6jGy5Z6FqWoyxOQMsSP>nAR87 zs`s-xzFT6Ph58~4En)S53?{Jq$(c~=JMwaGKIJY|WS{tO@WIut-+U92D(Wz0h;DLF-cOnogtXBLvZIN$4!0;8*-gCRfPQ|ORt`I zO&9t-NMj948jgyI-8`@b^@%A&xr1nZ;sr=7OENx%-273kD5fblk+kQ}53P;>V|_E* zxC%P9F!*gxNi{?!FPJO_F<>Rrpaj-%J}fM(!}LM+U4CF$y-&gR^uyIZfd^j!Cwhen zKi!A7eg#QM?-;==8&b1E_*JRvEjWOAm*EtFzH^TmSZ!+EAO^R&0+Z=Pygxv-3w>25 z@L>CLi$Rf!Ai(Xpkcc6C&@D~8771n-y0H-d%fu(hOJ2Fl*YQ&Xl#W3w^w1PhK^YyHXAo|C{SDT`PERR(>4 zY>ijv4UV-m8Z)j~4-~J!J}Cx29c^2D9HDl|;IQMtp}YvDiwGxhiJD)dQ8(qn)>^Y} z=1pl6HZ*6@K5!`6Lj+49b(hECsOxr~qwG+SXATcTuLlNm#YX5m)Wrke2%fv!W_Fx$ z=_Ky+Z76kM&E@Ff{Pk}sXKsHb(^`g-ZpO1aumi>J%P%?djr#WLQv2P5HVSLIIg@3m^~0A z9D2FyfSDuX6>JK`z*M0_Qn^&?WZaa=*3k>75{?Al=!c5XOub zyScgA+S=@3uo06iP_saxZ$fq(qE|jUbRFq$&txuUiO59B)IZoh>h@L1NLJd69t3u-a_0feZ=Uo08iDfYyh;Wme!{ zkuO8`uQa$XybOSqNJIya(!*^GqP!4co$zhemM|>|I(3z{qd~JsPd0nEh9~bNOsC?4 zq!R`fzJh}e$Qgm;JIlBx*e14+Ho}arcQ9jy2tF$?4;`+441S7dbA3%{<=lZ|N@|IWYU;}8bGXJD~Z*u?I@f>w_JA`)PyzXCzv`=jZACg}eLuKZ&c>RdMhSAM)3=nPpD zGK?<|hQ&dKTR1TvCFNoZGyMjE8~0;;an2v<>Lc;d`;`5Nys9%D)CF*k4vzG6>%h>^ zQyNP?l^ZjO(Y80i4MXx`ps#=4{mbWhPofl$H_TOdITB0z&P>95(907^=uH@qx6!t} zYxdtj%j=+YeWfd*cQ6&SzD!($@l;?3Q1t`W7l+1Cf$q(Bp1sMp6**&c@6-2DTx{b% zRFr=^WZX_P)`Iz?VJ!RrS26+^8RfZ20CcTbr8q1!H0F*KjB|2<@DwWs<1enIeWxzD zYQx#pjJ2?D!U8Vmy?4^7yi9uTJo#tR7D!T%eFj5HbZ+QVhS0RzN=l1%T$`FF&rw3# zprt3HPkM*7%TG^#ySu>oX?tJb78FD6yXIpj5;YlwXerABno?{lr*>xaiPEE>GzAU< z?o#0R@yr82wabAJW#E);TcG7LWmAK?w>`ngY_CsYfsf0b!yYI#^lMY|IT~jJjFqyk z{f*xYP|4csuZBEF^AO7LqXM@f2)X1Hsm!kIGKE04O{&NIE-5#X_f=tT5r;2G<#xit ztf~NGKQ0b&9JIItvV{}9(=h&^GYi8iTR7f{!z@km7xcR;P!%)i5XK84ZQKQfV` zxgFauC7)xKs~M%-Tw&r?Ic4Mt3@c-jHCsfks;fCR!a&5nvbGKWE*2)3h-VhN!z(%1 zndzB@uY9!)46;p{q6-uY-}xa!yG)NE^dF=+`tFlq7BOy}*A<*Fc_)$z3y*o9B9VBI znOgqY7Rb$~#_WS0_M#6^*8h%mHkqSt-LZUjT_e1>{Y1uzlP9f#8uY%Jb#AS6(QCu= z^>ct<&f2UmwaC&hx?O_eir_d9u#?pj*r=Pjb}UEDI}(rm7W5j1;S~&GiKPWYCx@;Z z8NKTv`ezlE>=z7tJmhfrVrt>5`rTsTIs5nTcmCYHm?LmB&T)=p;;}aQ;l-%^CY_eG z+Hy>OW^lnolU|HC^}b_E$)`|t1t6SEro#o30uHRdBhSMhhp!do%w*@dR>W$FoklT5 zRkZ9%QjIQ#F`^K%3WnTYohK2|n?}F`+YXjaZmtbVOeNd$$GElv05}ai)S_KWoo>(a z=U2$Aq>;zrfN%f;yzzi>&mSFq0mOMaAbl-E0lNmf_)b(!+BXBj}Qb{xN=9QZo;ol-Ge(UrpS zrC*Oz;uSDQWV-zrj_NzCTU?ExtfZiwK7UvYZpeImCfQ9zPrI(q?C+z}ZuG<`J(E7_ zukCD}#&r-*^Ld_q4_OU&+An*fB~GD+mYwzz z0-~t6OA8R8+T&w)XS$D*L0 z>_G_#79f}*fR&gxO##vjOxBAWAi zJrV$&V%4Y=`_;nD@pmy@Cud)vR^8IujEu{l97lEq7)8t?KU)Rfc<6y8T)wC3tD}l!W>*=$|Il=$LRw zNIiTSb(`z9wR;BKyRgF>=d#VUDfc`=YaHlV%&fJtT8j;t6a{4`v*#+{sxK$(In_R_ z+KphrBSr-W|AJT?%qF)NJsv-xSb+ScAR^*G*$Sr}FWLd_I(p6(61Lh)75RhlEx=?! z;#se&pf0{Wq<0JbDY{Rm>=R(sI05xhUDgks?}SSil{Kuy8B`Ri?{<_&_`eh2kOiwb z_~RFQ7j1uh`$FK;=5XOMZannr->VDkzZyHi6%pqdin)pTE!~MZ^BID6P4uGsg{7-W zvEAF(XZ{5@CaB(w zs<>FgooN5Aeo|=WyCyuRsOw`vL3RD)>|fQbH~piwKABvn?Y->q{OG=q?C%PeTscU} z9$^aO+J0X9!w0?MZEoT(W~(fAR~po3tY15Z)MqXa)WUtXD;8M6u70ik95TruC!zE#@j2_mIVra|6R$f8jz=-Kvq?K zL%E;h5h|;+dhb0zkWZXE`JT14J^7L{!IsCTVNdo=^kHSY z#kUY0=MoP%&dp8~J^}*NaxoQ3bCActu!BhA%zJZGMf+8$_8Xc!sJ>PJEdCQZ4WzFq zH*GoKGeGOxDMsk@>7CO)JP~Szj(2WgS4VEB_9XtrMrGG&u8DiRbc5CW>+V=<+b|rr ziv2a;K_TUFH#ss{7xvluu*ChrxB=GI&_nhJE;9ld3}1W~Le;-NtEg^Z#F|A`3XHIC zr0ownd=5A$XJ_ZkAq5|-0v$`sp2AG-TNB}RKS(fzG$qO#<^Lnva$|A(G~?#1U^L&p z5PNN0v#+fjDoJ1bu-5iev$;b;``H<1LItRHmWYJf?n|tF?cS9F5*7L6**1wBC{1L1 z_OY(XgBN!SzJBqFt;64h{ctJz;FHgO<}FvGukUQ^l-KqRARA&>r+KB_>u}&T`$V>< z{rID85`OYgQ(@|b1$~+WUqirKnx<9Cx(@m(%eO3DS`WF}!+Priw>;A#hcfyy8q9|uwyWUk%)^Jj!zriF70{QKK3KD%DOp#eboJ^z zI|g1w}eHL?X~)=@!mA zzk4g|Sj3Z`>*F%B>n)$3Z9inXXF#v;^1r|NJoLNvsgHH4uX0QQji}x6GY`7xnpe*? zV{*M4T}+2&(F6UUql0$u)e~hlmcQQJWGdIh%fzIWVa!Qm6@EU?V`t`E)Bko&x*PH% zQU=D;zBCDJL!-rZ)PfHY4$G`2jwhPmWs&Wjx7_`TGZ)^P`e8o0XID?|`E>}6_FA*p zNRDN!eK7tsY;S4!2C5|#9Nma%vv>qf7LUM)lp{Y0KyY>q2Kguoh`K0>q>H^_VlI*s z;Xp)$8s3YUv1HY%Rbmwed%vZTgMicdAXHB|qKxcNhIaI~v~BeXwFvPE!F02=-(D2%N;IA?j{Xy6@MbWkBVJCZildQ2^YYAm);#Z)qWtekXn% zM*1jGUxBQe zMVc{{;R5jPe?E-uG`!J$03+v+OJhBMWc)Rc=vUfy&vZ==1%{TT;jihD6nkmDb9>FS% z*JS+3U_o=gTfM}j6Xn(dgSE_zR0fol2A)+Wy@1Q8KJIp@wLKelDd`gH~&U8M8 z?l0s2t(1^|+IvQ%Yn5tV_K(gzd9p8Ys_n)*<9E%kNG2h9gI$8b>9sDe`zsHX)|?V)iKt59(bt9!Hs-hm6>aqHJ^ldPFptnXZVadM2=t?w zJ?G$UC<@G6d`wNd|CKe4@wXEg@1v1>oT5O3I!dfU6SVtb4cPmw0fTtKN~nOFVmsJ4 z;07QDv&o}Su2thYLqpW`Z!+AQW}O2!a9_}$F)bOvWe!EA*tG#vIXPm^MIYT#f=77dBoVOqhl@^{TmM3ZbjkWKv)hj1C3;Ax1mKk?UF&=&}s#i}nykF$`# z5$4fqA)@0E-~c-F(F*9ND16a&dF^>xNZjVlvyhPU@jL>Vgx3KBgw&ln4h%1RamS44NA|87(M^g5=Z9QjkSqW^bKH zAzT5YAm;k{`C(f^n9lY>uFg(IJH2YZ$4lasu^EuIGz@PB{$q!R)o*2llsPOY!B#Zf zQ$Saa(a0V>GqHFc%4`$`-be5;wGI*l3klhq)_Y(cf$j*8Kre+kB|KJJT0U4cr7-&7 zLMOHcw7)=V9qRKIl5in2RG@`q*O8AoTDa-R-b77G>K7+VMS_@UN78zQ)Ag4<@<`Ed z`TS94p)f$gcH~Fi>>9|x$@^0p)^VylCALEjU7nbW7*|-$!hDq=HLP0OI1m!9xCwG1 zk3gGct|4$@L4uNhy$}Hhv;Jx0eVepGR?$WdM?OY$NFqeP!8|`<;s}BWgZr}KtzNRS z?#4%)2X2RtC2ziV5O!Xqb8jPN%$l`=>WVNdbhq+m*DaOFRsUQkP&Ccngj5|?P1Irq zV)9>Kp=*n1q`27D4iXP5vbs!)hyNc={Ehy^*linsGL{9}MGfb7ooCOU&GsVAu6~z$ zL6*<1flZ9cm>`j>OzaXwlfLO8fdV6ZE!^ALu(f3ns4}aP(BvVl`~y)mxp5FGsT-^1 zXyNe)Xatz`Gef}26Tr^Jq^uO5pRo`4wCyxm+4)tEkPYv$X_~8YHkro1zK%1mK0JXA{p?{|+NU)x;8VZ0s zT59{oVn@C`NJbO|*~-(q)O>yiuzCn%$_KMEoO`#W+SnIuY_E*|=v&Vy**jNvn2h|$ z9||CoL%m5Ph1y)qFp9arLvgC)J}Cp!`Ejwaj(p(DyxCq07jAj~EE$&fY+5uvyEhJa ztt!u`?kP-zpZb`|{uaF6K!9 diff --git a/docs/assets/7-segment-clock/panel-sizes.png b/docs/assets/7-segment-clock/panel-sizes.png index 3827424c6179a22a061979f590ba4dc2bc88b501..ad6b85456f8696c23466243c58387ad669122bc6 100644 GIT binary patch literal 31455 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 diff --git a/docs/assets/7-segment-clock/separator.png b/docs/assets/7-segment-clock/separator.png index fb909da8ec6744e8fc05233ec6159799d836643a..c23cbcee38c255f9f68665eba88d5a019932e1a6 100644 GIT binary patch literal 7823 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 diff --git a/docs/assets/7-segment-clock/shots.json b/docs/assets/7-segment-clock/shots.json index 219d8159..b715dd82 100644 --- a/docs/assets/7-segment-clock/shots.json +++ b/docs/assets/7-segment-clock/shots.json @@ -289,12 +289,12 @@ { "shot": "narrow-spacing-2", "label": "64 x 32, spacing 2", - "sublabel": "fits with room to spare" + "sublabel": "the digits fill the height" }, { "shot": "narrow-spacing-10", "label": "64 x 32, spacing 10", - "sublabel": "too wide: the outer digits are clipped" + "sublabel": "still fits, but the digits shrink to make room" } ] } diff --git a/docs/assets/7-segment-clock/time-formats.png b/docs/assets/7-segment-clock/time-formats.png index e9bbf318b056f1b8a81ef67f9e7c5ff7a7a7de50..dfdd6a3b74662308f906f6182fb22e59c55ad863 100644 GIT binary patch literal 15436 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 diff --git a/docs/assets/christmas-countdown/countdown.png b/docs/assets/christmas-countdown/countdown.png index 4bbcab5bd783e4947c765edfb6e4698b2121b30d..421c6de1021a1f219871cf87c366d8e4d660dfde 100644 GIT binary patch literal 20705 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 DX}De+ literal 20386 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 F{}&iOe>wmF diff --git a/docs/assets/christmas-countdown/hero.png b/docs/assets/christmas-countdown/hero.png index 31795aa32c0edf4b2a00a6be24654584cbbb58db..f74f609782955d8775ba7989bf78c71c685f916a 100644 GIT binary patch literal 2837 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 literal 2986 zcmZ`*dsx!<8b>Xy(8^=enW;-?Hce?d!wbsV3elN5P072aMhR+p0WaX?H0z|PgCd8w z+?tm}AyG6%P|r>oD#yyuz}w0TGf*6bhKe3MJ$tr#-1m>)^ZVobyx;fzd_M2zeXsd? zyX)=RzY72W=y@Pd_yGV~>R&+j4o&qJHg#hW003U{IC1=RnrMD}w748k)LxvFHlTgn zusG4ROVZHq zCH0Gh|G|hO6LBM%ZnA+lKj8-pn7_pAnNo<8mFH(GmWi$+g5sPR@f(fJEVe!!-)0hQ zS>b~PuBr8A9@#SvnS30W&E_LZDI;Tz*anCM zXadr1zPbGzC>azkT(i^$Ib~-ZCO4yf+?gF6`n=0J)FZYDx3KvvLR(I+FUx>^v7y(v z5nP~?5|eNFB> zrxhb;P*C_p(a-T=AN!j1ZolE*n^8V#CL(=S3D5w1oC5&p{u%qrCz`8u+B(UtnG5Z; zAY}HwDKI4Qu z0v`yXss>ONLKq%IZkyzq^W;fG(4AE&truvbj#605)R(_+dk6PlD$Dt4`>=H!MG;z7 z)~>`5Ekhy|q-9Y1<1`yGW>kR`Wmi@(mJ(b0xvIQi)FGW5cM0QCv1zSANAv@O_c7n( z@NjTsZ*(1rM~mRmhlkK;i}vvYkJ{qbVz;3nV%)t+SCe2vvwcZmc-9Zlm~#h< z*P!-j5VdoIhqUcVg2PqkT*=kchFrml^YaXC-wb^8$PLSvBC$0u?NECFc701Gn}E`< zYg!S<>Juj62D~%T5Hg=b;6JU9Mmb!Ohkb42eUiB}-j2K?o+yY}E>P0N+)e`?Ul#iS zgJy9yELvJ2HW&AOx_kZ+sv~1$TAz#gH$3}8q~4YE{A!(GDVv{HVnRAnsE4q7JC99M zNxEiaD~ZJU1aRX6X$jw<+3477#gVg|tc?+p?x}$PqU|!HWCU0-41b3s8~4@Ja_g9T zddePCFP&_1?gZiGBeG$AGxSQ1D1M*M`-ZV;ZdI@hn|rv-joZ>axBJ^0-O38%?ci4Fz`*kDBnO_l`~LdU zS#UBjeHv;gH{=lJu$3<+kl+nA_hv~`C7V}Cv>Q~iJk<$srCp|8{<^;zQ@V)N@~e%~ z)J5CFTQ%U^xZ(4~$l>zvm7y_CfI07!9VWzy$A7~y=c)3%5ViqU0U<8R(ii--27v-; zC{faBVrHpgHP34Wkz^pGWvi|svlj1|oYd2GoZEOoP4eIR8Is`{%k4E!UOU|ggmzm647lB)H4h?@CM5S|#9)m2p|!?kUye)2r2O>S1^7 z{*rfTi@#~;>Uos=df*{t4I_FhX?pswy6Z6wD>}_I2fNbRFIJDYR+mzy-UygB4&(ow zg1s(~78MfLd@Pj9knrM|1VuPl5>n{3AVbKNK=Xe8&L1pQv^^687EFqm>#QpltG66$PE>Zc6`Y$mAIoG5@?)gb9^~+>`uS=0mB8j* z4l3o?_9b(LlrUaFgpki!uf~p=D1$OX+JVx-JZX`FC4U*BAfSk_!FIm{Ya-<->YF*B zCnK@!RRUu;MQtzwo*7N)X&3Hc=n3W2das+M;-St?NtR-QFFGx4!+B;0lx6xX%w{W< zIUTpT*Eh(=P^Noi$3Vsh2Cbewp7Cm35a*VLPzM$Qm*N}(LBccivG46E#!YaS^IB8F zfzru|@wUMLlL7m3HdPi(K=bVSlF*lsT@~#p)2XwCnC%==F=+{!p>5L5R2}u zXE0Hin4@q^JO~&zFDM7$jgoo7r(+0kKTwDt4d>ZJ4-4BHM@Co z@=-E9K&GmMfMK>pZ9zjGmoN0e{hTh$!(kele_fsa?}X=bO)LBUadS5OSZClZlqZhM z^b?%_Lhjhuxh5m1-S(M8fz&op(sMoS;I~79hDSy~7aT&k*%viK=OUAb8s)#m`YKfm5vGruchh#o5aLN{-Dob*0H|0?pI{{uFUFG&CZ diff --git a/docs/assets/christmas-countdown/panel-sizes.png b/docs/assets/christmas-countdown/panel-sizes.png index 6f0b9a79b29fc0f5641969bdcfb4aee1db4f82e4..167b7c351b00de12f15f18c6fa98759632202536 100644 GIT binary patch literal 31537 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�&f1dc8Ce$3(WBM{D! zCVm2mL>d|z^2}xcLvMPzaIsHb2G~h}Jjg4k+m};OHMJbdY}m6# zIAJQGH=$R{?;41JzrFE!AuGcl0#-Wf95~^?V7x=g)lNUMzMB8~YUQft5WxBdVKPtM z|Gv&ereB@9HuPFvhPCEJSu~V~H2K3z-1W&@KsbGfIbuqs^~^upW@-%el)WGzHLM`L ztL?zLM|ZVP0=T+`(U*3L4u!3Yo10(`qL;~L<&~Aym;YRJGWLqqa$Hn;rO*IY?I6l`RE$y&0Ug#JgaA~-k zA2wxCducVbTvtg$lV7j7=q}r^>ndc zH#U_YHwKr1W!@OEKBkkS)SQ(Sv*PRLc9cl7v5kX!<$1hPL0o;L;~z2%k^qBRW_Ngc z?~nXGN;U}mhrj3Msu2n9PAK$Jb*AXjcmPXBgGe*i(;*VBzDip0b#TClZ4!v!aqXoh zW5)!c0`bh&JbO_bbt^!akFI6B^3%p%=m{Sj-S{^Lc0b=8c z{Q^bSZ7s#6y~C8TFOKzp2;w=htg%xz_3vm{o(30hn*ew0fDM$IbIvR^8g89V6ui3p zt7LF*;vgr^2xslsg5(K;A*P>g)YpzU=5sFZFC#;-MMePg!I!@O zRfh3gi}yD=KM-{nC)Sq6Gn8d9a5OiExUjtDZ1%mRLH*?FgI#`htw3?e|6@f)XGSl& z$28ceF+}={UbdP44)C5sFPHjXC+h&}irzwGy!Rw#COHKCE|8zKGWQZawKC_tllKik z_M4ry1IT^~&kPj#a?HN>Lw#}V^ut>b7J42{{c$jlj-TALD)!c<@GNx#Lv`qS2sf4O zNP+8n`;(Weo3O`c>_Q69a({6gZcXW*FccKv7tu_EF?-5ICgi{99pLd8^?58`V`vTFktrwPu{v z^#-vO`JiuKZW~=1zo}o9<bJl<3~;cgvIs7zbEA8g#+%@tFI9MhMG8b3dlVb6 zRX@ z0TJ|B{qvDntz%#F2;Y6TRVzn{s9RGtiHfU0Qk{nE}zpP z85LZC%E+1&KD*n2Qi^YnTA52eSFX3Ivqug8@>l-CTf`|m@65o_Td%c_5w`tDAmlzX zHRF@xpZ9rA7W$_t>t0SS5ib_LiM$n3s{+Qb}<1YhLM1i~&fiiv9PCW2Oky#87O# zf%#)dLWv8s2X`6%eehWgDykx=tbOv6BM zSV*&stg^|yad#CJ=2}>iVHeTJ?g%XJ6y3H;j;I^Z%~D|4%|*hDzJqv%H>ttV;9#sU{Kw+~ivhEc61kA4MMiDv(bqN4H@=66GI*ZH&=n?FAuezS3e>wLc ze&&t(`l&2ABF&yUldl*eRK&pY0K%RZTI%CuXT%5rco1_D>_rRKr4(9ikcuz;Pm0ioRGm11Up!ROkM)*2s?H^Bfb3U%$Jgp6 zXm48Qdu~6T#Mo_}%B_4IOegK11~+ojhO8Ogrl%74dUtTR4QT><_q4kPSbWTYMgbgq zR-)RAc^PP$2-#XYv*^cvqx(H8((k7a9q_Ney0~;Z zC0Z?{SF0%454|)mCX)&`%^sL*Q9MCjj;*gaNYs6T1EZnl>7>JB?yUsJ;rMyb;1Lc- z5F~EBMu~i@YDu=kBhcrze17&L&YeIQG#m|;Q+1$mV$&VvA2Ju#05^mj0=OZ()}agC zG&Lim)rngxbCM<@2_`MU%%ek`XcFTr8EsLg;+ReT4)pA$)L+3|9`=$xr!4w0ySve| zz{0G-l>Z+$YZtPP%tFAzdRPeJQP-jELYL-mZ8D>F{Oa*=tHV>f8;XUQF-OWGRy0E@ zZ*IjsRbZ!dX8!&l!c;%bWDifzcS}ZZ5{Zn=B&*;(l{3qh>L*kYE}YSKy%Y7UL0z*_ zS#g2Oq!E|(u(7;Jzc!?@BKJ&apc?5H!|TJbq}4~sn*{{yGX|M5Gzdjcfc(4u{-2>M8$b2o u#T+PE*|_rAiwBTw9J=z^i$919TZDqrRUIB~mjd4xICIkU=fV@0!v7DA>?oQ5 literal 31462 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 literal 15483 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 J%(GX2`#)rhYsmlr diff --git a/docs/assets/hello-world/colors.png b/docs/assets/hello-world/colors.png index dbac319e97743132242b0ad5b67c38090edf62b9..4d21757d55cf3ad9488c7603f27ee6be42e275a0 100644 GIT binary patch literal 13384 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 G5B?8SK?s=u diff --git a/docs/assets/hello-world/hero.png b/docs/assets/hello-world/hero.png index df72947c64f739231e2a88f4dbab3e0a0787572f..ea751a7468994e61e7ff23d3a623fe7f04bae9c7 100644 GIT binary patch literal 1028 zcmeAS@N?(olHy`uVBq!ia0y~yU@ABId~5sI#sbnf;NZb|!_SA$eg0GDaD3yP^w8qxck*Lp z4j)_oPjJ)W*n+)vY5&CLwXgouUi(_&-ws=bhPxaN3=Iq{3~Y!bSEXBDC^2qif+tT{O2rwxcqL;0F!6={JwEYmdK II;Vst0D#FOX#fBK literal 927 zcmeAS@N?(olHy`uVBq!ia0y~yUNJY5_^DsH{K>zQ}hK!D}I zvWNfcBX=IRafu-(NFT|d=W@@2d9QcX*+07`_iOde{jd9(@4mF| z|D$tk&Ij4^HCMl%E>36FS7u;fQD9(THw+Wa#FfcF)Ff=f*FeorEf|w3l4k&cLPdIdM@}80b^+9>D~{P8cIVgTf3G!b_u8DT@f+)mSHDtQxih`)o%x!=XXSIW3Yj5hav-b_ z&^1ulwv+J)FOrQD1i>x^Ue6Q@CbEAC! z>g%1W3XL9an+~xE=2ejC)|JwXmTNzoZjaoYY@Gds=ln~hvvVJwn9PaY?I+xNH_P5O zFRqR-Eo(em8&{PG3cm)16c%^@f=s>0)cm3*wM;y7>$(3>Or_QtML2c@ss$aY980F#0d6FfZtW3wTn(b4SHtuI;LUteWiy%F{GPRQFc mOvsS}jdEf`ijL|UbpJ4%k9f4>aeB%WkhPw!elF{r5}E+6SmU_> diff --git a/docs/assets/hello-world/message-length.png b/docs/assets/hello-world/message-length.png index d17f292c87e3cd8d3ba6155fbb1254ffd9060178..db1ca2ce2b834871486a029fa57bde5fed1f01c3 100644 GIT binary patch literal 16573 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 fed44tSi<5b7?(xT4#NMIfJN(q?)ec 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 aJ`Rl?4v=ymfp5PNmo8|W&s4g8=l=lp6<@Fb diff --git a/docs/assets/hello-world/panel-sizes.png b/docs/assets/hello-world/panel-sizes.png index a28af52126970b78a554a1bff612ab17a6fc26aa..d15d272bc9883ccc024259f83ec3b16b4376b137 100644 GIT binary patch literal 22048 zcmeIa2UL_-*Y{0gj2I$JG)R;(q9O_cDk1{H7!XlViZtn>Qltt9NFAetL=dph+XzUn z()&ad5D<_qWk7nLp$|;`_F#gVa^Ls!y=6V`TF+W0!sX0$owCoq&i?KH*$>oJm9~Ai z`#TmEmTl+How>xq@{JJux5D~0T(NHGsAFN#9zTEPXRX^kvv{{FIvu}Aepoc$cl7$p zJ@N=ev%Nx`+-J@xo)Hp?PdTW~hShgCWB5)rvyJSf{6mEFR zc&|)6hBxk6#(mkt=PObDC!U(`n>l|w7WbyHVm#KP$!nOh*yo|TUwm-{9)5GR+7cRZ znhHO^=79grZ(?CVpnm`1|G?#x>iL9gwSo4PYh!OC#A}0)12{B;ygaR&-%DLv#JY_| z+&hKm)HVD;7C(+f(_2-ebQ;{t?PgnsmD6Iqn3q5-D9Y> zQF4IVuBqm=P$yX7L81>M8LJ6M+60b4!99KQM0r-H$PTZB^ji9$)>UBLH9%h}^4}@q z{^6G`-)`G|YbrIla-m*WL_eRE|IFUU{0jFCJ?E=&v{`$k=iE!@>CSCDC)L0Hisg>g zUc#Or`@Z6NG4FYQ!PwhF6{LPP{tH++uj$TAHQ8k?Uv}QJL3~Dz&Axo@md&xUTMY2U z#U}KHFeI8fKOBVO!{=Icr0JS?E#K$13rC^7YXf-=3amPy&Dl7*w0?xca1Gv}mKUD; zOYFBSchuXIP+4V!Quuf8)j{8CgPH*Dt|I#ZA2zPgzS%yf=Gp!V3elTX=E%0|xaGUP z5~sY}T)(34`A)6+i&a|o6+5KLHp@|FFc{2}mj@&zB{7e}!+(;u#Fs8dN?6Bt@54O8 zVzE(Cz0c3@z<Pb_4sdMQr78WZ7>dEDH&4kITuYdgkI?N~K zIC4=Ezcl18R#Q{6lh~H7oBdQ(dh&r_Y)Zq#pP`xDln^8r`Shu{ z=jvR&T1<3wUzSl>EfVcH@h+}q{!z6*m(mu6_?|-B-VD9GAUX0m$xSS$Q#KR5pFe-z zo~}zAi?XLrrDw~twk2H@K6r2fU*?*u8dLLd*A98hp8QE8SG>gaH;-}^b_oO^QQk`v zEgZ5-?;BO)Wo@2>^TF1EuL+4wVHU`Fdh6crT{y`vsKK6>T1JDkEg< zexm()X0J-<;RohHDWm~33iB@MV$_5^WqG>jYPFxPv6~rI5sd97j5@RcdGh8J?bz_C zmW0}UL8o%vm#1p^JTjDm_yo`);o++@dCdm|1n_7EO~#eb-UthUd#JWE(?CVhXCmm( zp+o!_JA5e^m(_1~zT(HcfB$}Bhu-6J&qosUssm*R#S`2k+qh3;)TfTWjaZzoMGm-) z$AB46cjsBmufV5Y-rHhfW`_45Ry2f(b-_AASk=DLWtu?9&fUi?5%QPITx?kf2k5f{ zdmq2aF@2>>co(POTjDf+R(Fb3;H(CjM8rXx4{0VXO}35aHOmLess510>~HryC~PlGW_&>{90+9k>`trIT<-39I&A zdz_KFdXDGXf^miW?E4qjnxdr=Rf}%V4_RlGB*C-zF<^H(nflAqU1GAbH0Yi&&YPB5 z)KC6!k0X#r$`LGYo64{3)!>GebeW#|-r;y}8ks|G^$KNWR`b#&=9imfh~+Ci)@I4^ zevv3N-2&^SIu`fM zV7}efBr7R%;=~CtF}3lIlcgLidw!ywyaM(ZmQO@HI3v#*aEzNVP)iOgo4DPfZG7%h ztAp!GsRyyYqPNH%m2*`^r?@jkxk2#20i%K0q-Kk3i*dxF$B68ts|BG?jGb>RxOhWij5T1cqRsdz$deCNk&Fo%a!b z=)*--u*%pNtLV4P!I&~}%x$vdLt>AZFZm`%w;mTxcQf?t3vWS+80Y82tGsEFBU0IV zz%SZ$=Z+3@D8XzW!l)B!a<#Yd!)%T##blit4c1gRSZ_49?88;y-&wveCR9XqG>~&Q z+95|-D(|#CTOk=g&M3L|;f}Z937cCM*<}MriCh~=PMO5XRJ z{BcdWi7NC{E=&Ct9`&;m^(w*!=Spaoa$AgV6S}Las*ZWhySmb>u_aCx=%cn)OSM?& zI$=gn+(K2%O;cN6Rb#WO;u_M|Px@Qs0z_Fs_^V?9mjf@FTCEWYwW!Ii+1t*KwgrA+$VRq@#C27}^u~|tI%BwuwK#nP`!>IHul76V{MSL3ELSpOu z)@H_F1jBoBtwvBHb)-r~&QQy4jkCa$HO@}CUL41mCE&|cjBVzdghR!IOPp8-U*F%B zjT@}_fmmL`9DXSn{+U~5<4@IYkn0=J?#$;quJ+D~PTnMNX&8v5+AKwY>M2rU0GCR@=$}=c&yu)UJE)oDG7Ej4@}Uqs9?T; z)Qo7yW@AEa+><~SmXk>qmGyNInLO5{aad(n1EFCnfo)(*=I~@yDG%{&*aDNJfA4EO= zvYqzu@DBa_)t7YR+lr%My$da~=(-3)Ev?HMlHka5Mw*tVc+r|91EmT{$I*IwA(YXf z`T4Eg-Qxk2#qbyzR}2DtVffq1*Gx&4wI?T4gVI#-9XHJj}mn)nYtk2g5jV;MrK zrizVA$W1;U87bw*h(}@zZnwn4w?n0GjE)A&dMF}&(_FuSJwqmkNr?6E{bj~#l@>yt zmH$a(=uY_&L0S7CyP$voe6gv*JU#Ws4StM>LE#y6U!|?VgIP{K&W;q*gy}3|q1`Og z3K4sc9!(rpl9!k7u;%lMsd$!?vpV3x_MJTI(W9d#Re`*{Cg|l5|DFQeb6R3Us3S9)Z&w;Pa6p8A%UVrkNoW7EEBZ*xq1t2Y39{_&}%8{ zv86GKSZyB8`hG3W5Y3etPA{Ic{vTLaYIUdwmmjI^-(F~o-zopoi6ym`?5mTLlk)& z-uwI**b!GwNr*|EQMW37)>ps(zyWUtjbvXbclhYhNpNAsYoUD2v_agu8IY!{UNKa! zXsg27p?W)Owm;uGELO6VyAo!6;>M6g2sSIYF-nq)lhf?_bx+D{<)zL5~gZz_%t}nZmr?ENL=ZE}MjqIc- z&bnF^`R`9*; zWz94g%C$8aoL=RrqleaAxnsKf6)t1DHQ#p-FRZOZWf#aX?m9I$_K#00pBRgkEwgBh zbXeom6%gR%l0QjbeF)})m6jNP)F;nXP_}>jgZ=y4OFrCXvuKXoC7+U#Abcp3*=rR} zdwh8KdxSjU?&bhB>D18@l;PYmbs{z0n=#X65PF15lQb%1wGwHE1YcHk}1Utg!Q}B#*c{O@%n~<#cw%B`fS~Zz0~vegNTDNw@M|+LcVDQXOSVwL*l%p7(ioTi^r*1(k@2 zk|xMP4^mbl_}*4}tFG@h**lbEKiBHVldddx_U(HT;g+j&Qpml3rL6_&mE$mcA0gkl zt1+A4I7{@VU~3)k^|jMNVKb`+j_lr)XJ@xaPZ4frZ9LWiwTT6@54FgtU387I!q#K_ zIjeD=vfqmuLLDRw0?xF;ne{HE;paovipY*Ki!%%i;?)6<>f0~7M44!Uw!;G z%g~nJzs$VX|6FE{!vDT8QFp5D$eo6rrI4$~{_Wk_G~r7cQcBaM?Ao%{$};VpltuN_ zOqydpp;^J%7%RrYdKT>96TV@>oOOJ&0drV8X{@zWceYO3cXG9k6r`PR8QR*N!gBfu z876ey2>v>u>z7}aBI9~81aIbdes!{MS*ou~8{esxk|I~&9P?<(1%*s=K4n!;v*+dh ztPvaN=*vdzX|Hrft+?{hrcMwmomu2FFE4l5_F~=cY;KuOZUyXrA(O^P-r(v#5k+w zS2U?*a5Yz!fYohu?9f|(cd$9Alz|PX|e5br~ z>=aTork!~RxJ?qHi|i~fC;rWP#`NUFHZf9+VVSw*Y9(V|UhJzDEA+Gnpv;7S;{m>z z_C}<=By)7w#ypD5RjnR0Mvh%h7gQQ^x^A?Gw3F+;(bvFYPt_*W}=@Uk&!pH|-T>ATLHcb65|Xkjs9*J{`wF9O)uZsCkPxAx0Kto8#ab1e&@``>h!^bu<}&`G1!>0jlTRV z3;p8LcdqI7#R;X{XiGvd)|$o2=c=Nkqqp$ges=f$@w1{zJH^VODK#v0w4ID3%A%) zi~1Z@&YrE2CG{?}@x;SzN+((&~2C&<@Gvk3bo`Zj6e0+Rt?8J@M{p8^~ zK`f7a_|a=;=jp(^IQZMMO>o!wf$>JlxZ3IH=*))#Ekw^(a|LiqiaM79>yzQu zZQ$tSq`>;%!2?PL;5_wBL$z=l^QLG$LiX8*KWQw;K0?6!Mdtvj9O~Q7gsnH`?bT4U zB}5gFT4lb4k*8{7qocC))#1nI_G8`w)>Vy>-nRRA%FHHxr}3snz=FqbT$&Gs9DyL_ zh*^EGmkx7YyTwgJjLT0-(-9p2ea#yqy_Q;(T95-^I?%^u;s8$3e`j4?9Ykp}12{&C zMgp@rx&KA3TrUis787}Fu|p1`j_-dua;4gjQw&FSyww2EkPn!Bn6djLa6LcZG-eG9 zYBK`@-C$`K>gs%gQjqsVoNv>q#kcM4?Lc;Iu*p#;Z1q`9Y2;;L-K@hxLOm8{u&vVH zj)D*Z>$TDi(2xlf`%1)1UjVEVt6aTmXGajuF3*J}gI)_mmw*}2hf(xvV3UclxbOJC z=Q%k?BMm_8L1E@XbVnbg0>eCR*S8vKV8=|SY~Q}!*kiU}y(8`SSAXEGWg$%0aI){J=A~A4QwLandX=7Q(5ZM-zx#A1K-dp&9`Cbw z8{U$GfpvZ#0T$Mos1gQ$5#GN@U`|nQ5rc$gkRTu>0pdlWaK&%`1s~ogt+k!*7N7aA z=&;vl2)J1GBBrzT%_)XR3%$~9QQctuHcB$P&(v?Hh&~3Lp>n&H*Y&u4|0xj>Td)D9 z(Eyakf%6%Xy>|8LeRf`0U~QsCr8eEU0;Thl$yg?|hvb+}#$}DPk+*N(%6XCVVKM%E z?^{UfKqAMBzBaC#sjnTf?y(N7M+yn6Q&OMp_t!W68{5Z?FyPLrhCH}!fms;1pX3;L+2u(5S;dB0VbAO3|&MQC{VEw7>|PjeV>>G=dZWvh#8(%$EIth)@9KLT7* z#8)gI?o|71LVkCyVm=A!iy%~vSH4I<)ejU1V{W?mBlB%N9OPp#yfQ8ichOpktaW@5 zL&H0~Z@DEWT?AI*xi;DdQK+x_5>Db*f8tHOTJy$S05E8S0>_!~AXj2PCpUL5;fYEV z)=lTae)HyjS)hH=u1z!RjsCYh_9j*agAfv<(lqup7|VB0R}9ss!%C2&`Qz>*>}Yk2 z8}AP~&9$ldsJ9=?a|ntp;_xO*@?#vEVb13Un~Yt9=o9xkZAH(WOUbjDe1Zz%t#BpA zBIMoOD@41D!!Ah*Y~QhVs}IX1@;39i`)I*Uw-}?O)P&xK*#bYIBR5auc5t6admhKa z@@(&mLx*l`aT$TPA>;=lV!Zl^TNvY{0ei@uHa5wLoj9=4D0!deJQX|~)Lgtev;xkY zjsI3jp|#D08SZS3C~*s&+Q2YkeKTZYj2xt5vMWzWNv##=nnWC>`R5r>pG~GFi|U7a zn2n1@L<9`nwte4@8v*)}(IC+UsmSRvVr4+ASxZ$Psh^kc!I`Y7KC`+ZMPh}_sS^Mb z5_yy0*>bL|{94Ah=LOo+T|LWO&^U}^mZU_%`KM3Eo3UIwcJ#&#kCwU4>X$fHLuN+5 zkUE%!FJp$|iWY}B2Xph9dm*sz1xRE)_K;1?E-W^-5~wYr%DHP`wNX;IpLeaPiC0Dp zH&j2QB3$HdW^fXPLdj6dn+4UtrKUYk#Tn%##T%!me2rC$yn(SVpsP}ITKTZh@$ z*uG<9ljD_isa`PqNAf5tH|ly-krh@`V(h0giSD@9-(dw8g?bb*1JdMzP0 zZ{Ez0LF{F6$}`@riz^e%gdYH(ABosd$Z=eUlw1pP9mO=b0TqL^%?Z1{%jLaE0o-lV ziCm0gNQWWhe>OFp$B}mah(TPvx|h<&^x+FtumtLqs{Jzc@}$A-oqu}ZqN<$P+Jc#z z+kk}yRuU%dY{0QFQ9Z*tJyE+Z`j+wh@A=E;dxTp|LW-&7;I=1l+!8jo@XPqlOe1bC zE-v}!tF0?VKEn1>XCnfHOGo^Ka&XKU^A+}<>8~go33`0)eW9}ftLgi9D8*~1-QC?i z<>lwuTLzMnlI;45`7w{4Jh?<%eF&Kxh^6b_yjfl8m1w~qJ2o}$h1K=c($Fw1VPw^_ z1A59TpUVwX*LoQ?rReXdCOf+X#C{j4F1*n#Dtp%b)s19qrPZh;yl#XJXUrXjEKvQL z=cQZ24t357tgx4WWE(sG^XEx+{n1?V=ai+h{t7^8wu8mRtCtEyic%9^ahWw-xR8y?Qm}R_Swiq2vScL%@!-YFVcd4uFpzBNA^%w{0 zd~LQtnH?kpE^WiP%W%l|`VSj??7z?DUy)x##v4J}`VW7N-ToV1zN62d z6>6lWq0!O)J9%>QK5mr|(WtT??om1i)}|KMT21~I4#3Q9ATP`{ zCp?E$k%9!Va{BAv2HZaJw2iU(60z*LOd7tdgCAqdwlsF?^Cc+Etx1PZOY2XYuX%Rfw34GooZBpQt`_Pj6t;97$?}zWwvJf^L4_tcK1X%S34{UPoqy1M8fBp~e(dYJLeLzij$?j{rsvhGT=MH@lJxn*?V| z4Rup~S*(m2puI2WpvAZvWi>2sf5_aYZp`doS09YQSEr86-U!(YGhuP*b5 zIz+MrM#sl1XsYfXD(w^W?H{ybyz(-t?V&m46P_w`&?yECg$|t3bonR1^bh;y(>a7K z2==SSYYGx#^f)10Qnt8y+c+iLZkmsWKml?WcQ&&@15X#7wO^B<5joe<+uw9{gJCf{ zI~Z;_u#acVjci?Cx@`=ftvBt5x#Gn^<^A?QF;kYi&fHddw4hFD@hLTDI+Z|lSL)pQ z%erxXY112yWBms(&O7SUoiXnR)+pA~(t-ufUJqa4gM2CU`LzPVcZiK?`kQHH&K`3p zFt1KE(^~YyuAm#F`WBbEf}KyuZ!__I>CVQ}Kzcwy{s|p!u+I&$1k3Es6QCR?SeKuFlcnltiRdrRKLs|fv=Dnrq`L-aTmQzB{I3|#f6dhX z`f2svYTl9gFyl_WeSqJzf2ezVN{=bl`IM{q|J^#Ce858NyN+G&%+PB-wRoX!7IZv8 zvMcXjKiB~w&KjYN$fO4X>ky8LV~`jif0PAzCXhBdxeI64p3?Gj=ktG-d#ZHoguu^p zh`sVxkQgu_+*AkXql{qVEr8~Ha0XaiE3g@+U=zYo)LNa|x9mJW$*%%0lV=4A^pl+D z?F$#)vWb=FT(5fqm}ePcA9x&!J|6^7&+AG^2?YLV`XZJA60@I9-JXMxm;T!(j%eUT zfYKV8Vq{bZzpF#FNFWN_=8pubp#+*v2=nhHW4+xMM(PH!NpEEFBj74S z>T@b{CDO|VXsW-D!nK-*5wIsK!R`L2)^vqWMr7i~Gkf_lF0(z(Q|&iEUS@`TC(WQPa)}v7$HdS8Q8oesg&_?(weSW|M5w5d zez_V%M4*2IZRjzVnag-c1r%6}rGsb_0OU@_d*CYnTL4~evdn)dFnvd2VynI~q;~KZ zD$W)W07o96#5HlBOf2em7cPbUsR&~c#n4n&SD$)654uD?Su#we^x|7_O-)Ut$IJ^y zwZ{;F02ht|%`;%w@Axl7oydIl>=}@U7-{F-;^r4v;{f2f;+?x5@hh;ecDTUyQESop zGweqp;&8XOr$JO-u{z{mQc{AVbT`|9@{tLdVMZDANXFxs7^6ZAU}{2lUa|*?uxShX zVQianqgLVz!LdmlNY%+v!g*DFf)qTzwOBkHQC+C-HgM)(+@&J`!$vF4g7AdA{ zMPI&p?OH=%5x6CZW8O>CT|@x%<-|$~2#k#!J@U*OnM~MGF;L2B41;KGZIzEO4FG{J zlgrl!A7UbMkh_M880f5=VMp9Jrr*`r>fdt z>E*Fn8w??$fJ!LlnE%#7m<5p6!tjQfc(2|0YLolYgvRB|m#vDn?l^FYI`^u(ySw_H zAi#bO{)b4E9OfOc%1acOhF)>=wWpv%&bXP7AeO<-W_p+Yc7$0DH8zJ^TMsKVtw z5Y?k7GkHS=a(_rWXRA463heM?(4trZ@YL7u0cZ*;@jUQFOaV@{N6qWk-}6et@=P)+ zy#?f2<<@YU8VV2p4rnm*@{4$_I*_iS=U#3BK^*qU6Q-)&wcl;Lpvzbo7>lwkh~*=H zWTvC5T4HWJCEqwC6 zg+Xbp6i`5Pxt7({e$R480fCxIdd|0;-LCZhD-*|A>-023jo+c+(M>FSghY8(8C2E( zHE5`1sOqxfHg56`B&{wmlSuSQra$J#EG@YkWmSkK+9~fpurOJS64MXwJ(xEiQ$dI& zhecsgqiqd}Ld!=+8^S_luO+;Fo5u4~ z{X>Idcmv7xxaz822l>v~xWZ zM|e6=CfkyI5T%B_5&$6sx_lcW#M7_UHn@(CLnc;k_H^om45)?Url)JI5+Xgt>G39ks_rRd$NG6Y+yDUO3E( z%^sP}))Yo*u*QKD=769eZe>bXJPGXpocOrxa=Rj%Q}#@sfOy_KclNd@m?+MMFz1r7 zQW$=aofjpFCL31__ZC>A1S}luR76P+*VfjwrE+XSd&fbn+_8oTn6zcR>G;qhpR|MP0ggz=EnLUGwC7>vK_;7;dQCAX8j43rIq5O>1Br(4%yqd z0WW71auvWAnExIhAOFNzG|NCSAg6gHwH%fYtHgZAVsvyJXf3z$A2tm4@H~5#$!N}< z9FGp&A?T^ErB!Uv44&R1#@WSxh{pEackm|?>*fNl6uDh8?yu*H;(G-zMjmmhSprSt zW}nTesj2)_+E#4#7S#H6Sq<>-x_;8OttMb{xmCC%`U#go-7aBN&fXA1 zBYl1SQSAVNiQB$2A(j{>w-gFh*9g+;r%?Lhv~V^9g_%!6Q=k8k_FitZ(AL!SG3uN3 zSKnWeC2lb>Ik{GFKZX&r>(D&B=+TSIrr_mZF;+JgE!_sWZ%FVh!*S6r>U{NhR~9X+ zt}dCX(z&ae0%FHd3K6m55IX}4?F@?vFk`iSF- zTMYn6FTVA;0(E+bZh=q$A_Op*S;H7YUfF7DtWjGi?g?LNIf+#ADiCK8+iv;h{KfPc zjo<#5ny+DR^!k;qkI&{7)*pmoD3+700-4RYJ(x$29^IsEla#pOs0>M5oA)WMSV~)4 z+v1|Dcb=!))k~LH<5XjSJGU@?=JQ%e7ILcA@s6ibV?g101H|Qz&t0&wnbD5PuB(^6 z)*Oq6T|qI`Fa61rKsJ6Jv}cXtY>!$@q-M2B7WLtlcRC>{SGChGCHnUmZGUiP`}Qfr zn=gIxEE+SEw~ns7`Po=wl`_{A2+P~(R*OJO?Rb+le(bm1gDj=nnH%i0s1tVYi&x5s z^a7HXeZ{<$K$+K(DSW!L>m=y^;?<7qw99S$s6rz6*`C$8)9E*n2*VETsVJGmO*7RM$d-OtY(FD3KqbTgE7s< zdQk4GFD>gXHh6W#mv8s0UpdF=fyVYDB^IVcjfg~wI=u#MHZ%9=OoWmXJ+r(lr@v6vOXSd2o0oMpMmJ> zINL`hAlwTe>pFzs$J~p)Et&4dCc?_Xb8qKpQy1&aA9ZB0tKF6Xa77c)Y2Mk7xb@Hb z1K2`r#N}VIg^kOvIiN-B5Bd_Q{3~Gd*C_gbKK=jvn5ah-@7C&qcc~X)$AdFgeI-^` zWpt|tt+7)k3V}XqKX+5c&++--d?iM4IrOWWK4BatYT6)BEOALqp4E>Xu81$D@P8ok zf2Jz?@^dfW>MFH8j^39z^S#J~S)XsmxLk(vHjaS{ngzpo*ZM{NBxT&-WP8Hbgu*#T z_KZ7AX^jh&RUba@zKdkqSjrJJ;yh{QWcAgjhJ86D@W3tUtTm6=8=QQ-*b_1S9Topkh_``$Ke64r z5!qE5(WJ4tMvT*yJb#`_vR2|qT6;-pN6vwjW4^jE{zOdadhw8MRL*$*Qtdxnx5Z6gqI^ zt)lOg>XA*CoC(>RC~WF@xqF`x_w{dXSmQV1CO=qBoYEacH1<2)vbdC%DZZ#x)cxwM z(`fcL|2Wm~V1(5?k4%T{fy5~t>?_GAz1b9wEvTTr7vfz!e^NGY*u?20i=5x&UE@u1 zWVf+POpr0qUB>E8sTWMeO=b7EqoQ_w|C#lDL6n(mE3GUyZZ>0y7C_~xH7DzNbVu83x+_l}38WiEcr``%MeyQEdYxhNdH^|OWd*=vp& z9V5Wn`P<*_^0k^ie7=KG`c_Rl#$b4^LkYL(PvrlLDgKlY_@B#vJ+JVejLDtVxUI0U zU{3#r@BSOl_}M<>^G~nn_WjIY#bh_YW2$j6Acs7A_H3#`795CV9(-rufyM(zGcz+A9UZ+G zacmS0u6F}|df7hi2kKa$S5P&;Wrn$py$i(;n1V7PZP>f9r!#ktTbw=jAA{4wX9IT2 z0wekz!;4m~4w=xVbX&5Q0rW;3y;cMGn-qqlCXQ$TKLd@#cl>=Iwqy!<8FRiU(48`t z+SJfcasiJ2xB@bt=_^_MZR#_jEY@@3JSba1t_X0F_J>e54F)YPGRXTDv=20o5T`T9 z)7c8FAYDR2z{Bg>_XACJ0CEK9_8$YZ3CGVvWeWjq<5sK1m}eYG{T^cOGw@8T`&24B zuk=IUv!_K5h!UW!qfjA$x;h-BRFt7#Fa?^%ioyHbW6f9-H*Ek|>Wu@mKPn}dOenAd z34oqmj6D8;9v%_F450_;I)oQpP#gdbxS{|L4v7rc1Wfl95vzRIR0t<5-#vron46h# z+V6tQ1DAZ7rOmOR%3~%t5>(*iI4l3rqYhZ+aLY>|X;2yA%&nRD_rFVG=Yr+~ zjLKXJpv*xfg1$qWW?dOLto{FudN6ncKB8o(rz2s4$Ba0n=nTaw_4qu>FY zEALzd1#K*x?q(Kf066XE=Lb6Hu2jur69%~p_@$_J1hbVEAj))Xk^2*mS8YQSRo4-_lMU?EnEVv#thYyda}?8K!1MPa9Mz=C6bP zTzx^TzYgEbALQ4>G|_u*=kUcb9vn>2MVv9+8Qn8BnNV7 zC=TERr8PjZ)@Q*q=g1#WppADO^xuyVZOsuwx%YO%M_(AAZEN3KDU~j#+6CF>)~qpF?h>1jY$R zU(WXZepfDh^s7zZzJ497xd?LrH6i{i(1lZLzYBE7Rt@YcmS1i`LfM27$b;<;Mex47 zTz=aD5)h&|>H^?@Ny~R1AoEl)TiAOxkY^42hc9BMsNs891n?l+fl67Bt^xRmvGOFr zkwdV`C&`wr&+CY21_dUz0$e1R4xG>@cBFT~iEyY~v!njPE9XXuu3NJ`5=^mCJM)@9H-l_74hzQ7) z+<nY!4VtzVF~?$XnoKbCX2J!JIx%j%0Q&Zjrt{II%ZaQ7-ht`pbi zQm4slhg z+#z3_F+3WeX7waC)_CF3P($;d%Qnn*=M`xUnu)Dtp=ZTi2{TYsAs}?F&Dga$M%H@l z?PQ`KQ?8A|?=|aj1JZ70-4DlVZN%+*OAE9T@K8ZwE7r>$jvT|Ge;X)r(tSToQ}9I_ zr>=z5S}-73KRZFF;E}>a_4lOcrPKD*u_#Vrpr^%nU`&{a!)QKEbsK}ye$fgt986rT zr3GaM{#n^e*s($Nl`Gt9p=gRX*cv~^y3`oS#ZBtTiE|o%YvrL)wzRZlsZl-ZT1oB! zFxqA8WmdB!)?_z-FDW^eJR2z?pd!OOuPjFQxM4P}XIR0+J zv_eRlTb+~hKPZ*Ex7XvH$V)hviQ8*XAqh^*iV<&(a%WdC6Q56fe?+ykFBZxe)7w;@ z3SPAFY&}078zl=m_e(xu6(gWK7;9{P_$>}jVX%kMCSGxz6%Y^z5whyhH+L%u!6w>~S+A)8H8SwU z4hpRN0s@Ks^SfFYMB~Ez*RQ?0@@6!&-TISfG+O+&ag&oL!e(HBtzs~aL7A4#c}-S7 zJUHVrI?gy!)&aFs6xP~Y1pdB^LRzbYiMLddqQ8?Pm!}bqz7_Ij{T9%}#nJ-nhR#!> zqLqE$rk*Pa!!>(v0lj!0he=23Q-u6}GNU9RXdpOdpTvO!F8>VX&(}fGoXanmQeC6% z^82Ag0#uoik8n#56)|ds^A-CZhr`Y$UE1WS_Rp)SS7p=Vb8_~Oy=1kd<2wJQu!5t? zLDy#3#0h6jQCoZy%wQOC&`M*mpq*j4Cyl*qxZtSf~4NmCd57TQcv<8v1 z1(z)9?)3MTqdRkd?^goy@A3Um7iXajWW+ZF!$7nrKyK~c%jth((j7hEyWk4(0*V-{85z1e@HV==j9E9PfFC>B`R1C%1)nn$k9P5j)#satB@Q} znU(VBd}v@dD`l~gF)>&5aI!6H)}0n*U#_$8$Wm$`w3Uq*vXHf=~XD_no2F#V+juPjM)equ)7d5rdESqBO*!3A8-K-N9QrWNT zQ=4XQp3EAxj0sDiNC}mo6?^fU3W!d(_A~lRE&jxnSo>)#R$+dz0sR9O8P@e~FUPt5 z^ob3Uw1F*{THcgN_S!xwXXd77F$H;a*?gKo$YSVW3-q)eo^aIq!Doz*2_{a@G1aK^ zPUDsqGy^YkQDLRn^BDdwPwDTuP1+=%897eBiP~h8OTD(X%W^gYovy&>T%x=#Q~Q(R zm8p+^>ACW9u@iG`K{nvppd85<@lgu80TtIjvp9t5dEs64;vG>SyG2HIwU_nZ0$(^~ z&NBEY%j4#eX?`s;W9q#_j4PelC!dUGvd_+VXO(BNlU@`Qpsm}+-;kyL^vrLF@ZB5~ z((}kDPo_n1jO1Cb59N2FecWEp&SIQoaQxx0#u zGF^jN<8f&WdhE1iLG#P}^V(A;8f)4m{r9AH{wX`aoJ`gZvR6>rDSwj?8V5;clP{%& zp|DJNNiVd$h}IpvnXjfz=y>I2^3^)_`-E^`!ZYLavwPKBoK}3N@b{-yX6S?7$<;kS zc38~*@TVjJbE@{-7De$yxN@_*u5DRty5M{emy|x?#czG*Pn4B~dFR1{Trc`t>AN8m4 zW{%-E#9!KTEC@l@@$BXS{xn-BA_{!NAHTCdFaFqNgwa^VXV9Din? z8efp#RH#;`(TD5r?A-d&OB|4$!u)=quZy&!R7fgxtUrqL(513aX`c)8!x@iX4 z9;*$4E+!ujA=3ZG*xXFa8+$$>!pVuM~ zuTJs1X>_!tYDH4~DZVl0J~pk-&U#LE(>`eO`ZH(nQPHt-c7_X6eQ?dacB_#fxq&v- z=R4N+JW<6{=j6$^@7~2KI1wpm4ui>LjyS1!On423E6k6!W>_^uGv+5vuzvLSv9jZ|ZX>ItaXwq>qb{p~UI)Q6Os z1ssQEUgdK(_K^6}+ts|PA22A~2q9cRLHOX6W6A3Nc@^XC>%+;#aYNx!t7eQ?y;Z-=X*rBItf!KpXbM#VAWm2$N@W3>!EX_tYALJ%9*;hwzdaiS{_rqN`ccKEhC49h71^wKYsk^*jH}mJrpGxxK+~7zUyWB z*r6fPV%CLYU|yN1&Va*q9s8A(lmeaG4UB5*>Q0Cx9J123x1S&c%op%!Kxc@cnKO%v zi+B;Vs+3f2uz`@O*L1U%2&<10z%L?#9?vZ~A)|{bb?%RM!d{x3EwCm`wLRD8>HmQpudNeRFpuyuw^7rh_Gvk*rSM{BrNX?rZj6^0WIGOYGN1Hg7eY(AV-MV$x-am0^ zvzD{}==|ZAqp2stv*s3`9JIXF7#9^C9i5zvCMSA!FxzDih+p!^S}rawb0H<90N=iH zcNNbmKU%wyCeOix2OWBfh2(789`DtkpXy@_if~GEa&qJ)nXSp}AEcl{PmhPWy17Lb zeh&O@iw|wg9quC}BXO4suJNkPo#U`s4QOA+WC?QS&6b&&nQX({xWhIrvqKF+HZUVd z^n7@Jb#*nL#-lxFpGH1L(ezABRek10_%vKyXPI#$pQ5!E^y0l3afoNke z75x^cZ?9Re!2{EMidf|vwoU5kLP=ktjlPG6hvIyfYVd{~n>D<~o@a!HhVDFk{eb_{ z+_xKdUZ@)I8-5?F<}-J_CG8Y-xKTnkNzh-_Cu_I7{5;W@vQc!uS^3=SwIciX?sd7h z3h_(C&xN+_Q(j4=XJ_MKN!bgZ3yOPZYDC!1%}GI&8_AoT*eoj=sXBWy-Q-$rZLQR$muW4n3NU~yS||SxKg^A_kJHJ?m~ys~+psEg zx+X#afe2jk9qen2S0LSJJ-dF}{ac*Gh^tN zSKEJc3}NOdMKp@Er;h3hy=AO5mY)$M&O*ODYNz=L8ozeeuCDhnV!yBxQpK; zv@e%oa=JZrKHfUXV?P3MyFdh`YiwNbEwe#ohceu77I?_TeT((Il>hZ5g~ zJRHGaNqU>S$PmSLR z*%TQT=1ZQ$@o(D}vBTHE-oE!~r23*)bBI=VScA04#|0DCPPtIC;v4TOJxO`<53&Sy zXk728q?cybp$7#>u>Eu;B(|5jFa_K=hVl76pR`&z^J5b!H78){*l)6Qd`^7xmPaST zg?oq+ASy2>Y+ZJItuNtoBlp^8y5G8Nyu$z)1oUW)s; ze3bBW4>7R^Zv&lBoc1vTnI<3XrGjBn(p=(BkFtS_$K5GJDxJ)}fB*hspNXSMl}~$g zV?54nuyMRI*WYr=?rhu}lx#rttD{DhxXS@WVtjWGXIk{{vujfCm{jF3*Q4dA&j$vc zhRPU^W*Vq%YmX2>AYR8{BF1WX`G?6AQn8eRtXQfy)vc${MqtYp>g(!-)7qg%d8Wek z8U@!H_eqn=&-Mgw6bN`R&4TANc3nD+ySUk(nW?)$w35+eQl_MI{P^+FQOTB;I3aSh z4Ew5{o?dedDsR7(6l$4PUA9J(j2=$s+*hJA=!?S>&-90;=^~!8BY_LPoGx9*@R6{Ou24Qz_JG1`%<1?_&M|4`xTHDPkJ$Xh}r z``r2Sl};q$2;q3`ad=*>LIkG#U~KFT3d@q1Hty72WJfk}YU-=9Gtz15(2l%N@nG+} zy%io)8&dX{XWhPiJ23E#@sul%1qWB0`{Knfa6)*;k-f<&C|2|J(CUOeh(E7z7?Ypq zOP$5my*1*t;ccX4Gltlh9Q|AxYx$Ds{q4{Q}) zzQ{aqaPO$yglo|}g%sn{q$GOmY2y8dK25|bZ;7k@P#FW(Wf9UU);UsWr4i-?mhFPJu>q0Q@7VkPu!RhFY>#h?W%qB z^{w87T+zqwlig}dmu+lDr}`?EXgF2atKzE|dn7UhTRVhLZ4}TqG>ndq*Pq#>vsDD9 zgMziSxVSj&%LC_`mR&<*YY(WX1Rl6L@}e$$P+wm^gjb~OGnJt8wpzkNC330hx$?s= zqrUGF)_#S4h`{R zDqVYD^ow=mnlWIBhZWY2`UZnF?n}2T5z(oz!_QBm0%D0NHQvp5lHVXL-zXgC_v)2+ zGP>B?&10j9$@%l94gNVEmQ6^`Z99oG_rk+_$}I7{ot$kNnOp{f{yPp7h6gKR@9E32Mj2Va+t_3j?nu%+Mb0(Xl?Vr&9`oZnOKHo`i$2$e4I z4MJx$217(s&fej1-nLWq#=WD6U8Qq^UvpZP-Ix_S?(!bI3klmZ_g5X)wZf0s_Xv-5GK#S|$+8UU6l?YJOP32PUz)}57CfoDd-Hc%rA}3o ztNe(ob}2Ic=-#b-ZJ@F`1)onOvrjHGklUOqo9+#|mq#>=`fvot72AqjvSpGz5j;&? zm+)`=zW%H487@z!DkuTW6!S*@elKrM+t5NJ{fJKE%s*~^n)qk;tmus8?)ioq5UA{j z^S(_Nx9vaUk4ns>OE$^lmjb3q;$M6Bm;PGzIs4lP43``#Z^8s!Fv6WB;T!O2YuZ-_}(*Cxog9L_yF=d#1ZHDzxSbehFLc) zEdqF9G2(XFa&?vqD~Eg8>b~Bs|B0~$Zr60Sudk1Apr1^0bRds*3*)l`s?cNIE=qcX zZZDDi>Ym&K-B(TgpRd1pnaHct#$xDps;;)~Q5A5X^or$(wq<*(M!2iFPr9u|yzcsY zzOd`b!kx?Jx1IM5hbi_9AMYOzFCYkUH#m6jingt261+FkIk<`P{d=4qrjCBG6GbBN z4j*6jpA5>;Oe3mjLZ0{TvF^%^);*OwXl7+$x$PQ{id5+x#J{%uoN&e>mA z2d^7;BQxpzp#{l&W~jizA3Bu{;qMeCzhJXpx}^5Bqvld?l5w$p znG*>+)DS~RPy&Kg;CBnq)R{!(BK-^M)DFfFlqHn4vwe>PwvBl0H0@2_E$ZaJ}ju?*-VgL3EJ^)|!bbk$cCJc4##|N{dZT$Qs*9Iy4rCk7#K5bO!JvZ}!faLhWbBell z!}iwJC4-_(bWz_)B;PC6GJsW%`-eY&FSax{N_{U&2;_r<#vYx7?W{>5&))Cv9_zrM zkU#|NFjXFeBsK6(ONc(>E{-ig$EE;}v004BopQFcv3AiwaFg)~^r*L!gm(Kmw3M9XR?wEKu zi-XZ<@Pup&PiAECjU5g@w#L7XY}$t9rw+e!L^s z+uIuk4W2#%9d0_Qt#}C?rb+gsiE0SKx=AqA2eBv1>_jl>}iU~I$)51 zOZlqDF}MI~qk^@SRTL)+%n4waXy5|TS0>PT`qS&6_58tn${rKoNnD)lex{RX%%%b) zthh0HzUH)us@DNoS?Xv;as;~o=A>31w;kAFEkKcAs{$Gq%WsU{;Z?r~Ha=5Q;V~J? zc7|6;h>I6F_W4u?ekU3T?DU6i+YHTmGWAlw0=lwF-i3yTCn~vJixgJl0=i0X>PSq5 z22ZoTxSZ3MF&RxKo0Z?NAy#_xq!?k$C5`g-qy3r^lz5TM=`4e6BAArRU|qNfGJv@_ zGlXL`ItAz(8ynjgfQy7=&qQkSz@$Vo2Sos;D8mfkqE}#0n95o0T5<5l?~7_aO*!uf zW856pU`O0d`4$e#w7Chqotb^j%>q;lK#ENI9?R{^tyg*op|~Ik!$PdY~nCfj9>;-1Cd*|em#{+?aaAEZ9uVSg9RLa z`st?wI5^-Wm_IPg?cT8l){=xB3Pkw#-)=_{6M#u{Po6yREaS8k9v2;U{Kt)sxWOjv zNA;@_uV#K$Y9cS-?2o{d;6JUdQV=q_zpF8{zJ3#DI5xvy16t=!p3wRgU&#hMX?ocA z4dUjbEQ>1xog9zGv!uk9GO9Sg=|`6jTQ#|?1;>tY=qtBsNfQX!Ae5AlPzjz5pT_y~ zv+i5=m?;fsfiL~8t6Ut3#IhwP@VJPO4J&HHcE!w7kDj@q&@{LL*9Yb& zRdOyBK%6F33xmdTh)DkTxX%xz-|gKiAb-`c!hx|s$@aLA-59A|Q{iCZ*j`iU=|p^B zu3TOnhX>QHwke~whlohOCy;RP>uV=YP>0FIHm&k|_6*+%qQpG@0b^RG`Zx$yF9TDo zL3YQfcki5V^txNC*8p_ zzyM5&9Jgqs`7@|MpG0g}XX6gX0y-Mu&_yv9D4M$ih}!d=MMz&Ma;F} zmASS)HkJU&Vc5))6Xp)=Eu=AN62P)gMSf^Jbjwptsu>1Dyvu}QCwR`jP`4U(cHgA4}?W%CH zx3@1^f7QxL?)rzzZ$I?I#+GYbGD@tnhQJDs#oG@S;R5)KXU{f?sJp%+OALFI;0uOB zD`J7nC@|*wURt^FSZH25u`s3PN$EB)HugC)E~0w%qK1Zsw)WW1-={{)Fn)x_C7eI! zyi38_j>pQZ!UJPr`P+zxwX@S#AwJz0#?M-v?(tNlO!O^GNm?E zM!Drn;WU5D&U;??Dn&CL9ZO>>7#AhYi!9s|L0V9Vl{F!}Rf^;EXer!5;6i1i6(xBn z+*E6Aa>9KxwIn&C-o|hN`Ly)(<4)U)AY%0h4*iN2 z3dE|BfxHuZ2t?C9JDfJsff`WBA;xb4cPl~sTVA0prd*=(O-vHF^8d%3nVZV{>-f$^ zK8}hqV0i7`tRyx5TxQU1w}dZxynEjH8aj_qOsL?nz0evf(ZiqpQ*cWWI*-NrB-j@z zc=R(z-cQs`Uva>fnGLt;T*sfseGxm0FIUqlN2y2f7a`3v#!D0O76|zfnq~qD9L_(! zpq6D9c)76bg&sLTS=+V-??YPJ;!awCAOD3xUYaeI2j9TRkjA0kuXVMJ;Qtqc_zPzX z-11#*{fx#z))t-WN(JjN@>}Kx6ovH(cbYctb@V@I{!Ql-Bn<21S3#10asbsh;UisA zr>(R-W)%bje02a&$;vcKtj;AN}-1d;x2Q~%p6VA)*1-c`#}ePw}L zo#n~AT=@SyZ~_LOKoH?Z6o6dVihYS?z}^Aj3&02~+WcQ@YQl~yI&GnVkSM)q4n&6O zMy4*u>34)wJV0_Iz?d6t1&I@|94Qo*tD%6;(E1JA4_xL7sFdCJ?cIAyR~J1TYu(b? zs{3|+Zlrn8m&72Z$C!5)+O)zh&-JB2VdIi+1xM5ND&GaTX3%>LeBX#+tvlat4BjN9 zY*qg_NzFHy55k_3?D<=3g-0NoJVyq%J97KMr@G1No#16cv|5t~ws=mhOaQQ-r|V5! zYPSM$su+qkiNGBJF0?pZ!yAJs1Kt5K=gh+QuWqddQPh+WbXN+9xU#GXNxJIvtA%P= z_4V~W)34X2w$J>}V5Q^yW`{vga&q@e5G{STWzQVw$iOiNFFL364EVg1GO}<8%;hot z06}#>MKQ5K9D5uGbR#tjmw3>!4mefxqeuD|FD?#*$(#^5bM|aFn@>PMKx6UqBV9P9 zcY@yq0;ZAC$64dC%I>y4+|N>@5-#l%ZEaq*jbrHr{H|GoAqXdfvn)UZ)EVD?dM7{82h;Ru#7 zWe0$U{txiLIInpF?#IL|)JtN#U|t@{^o&XB%z-y?bul~^K?{rxcu0VwY*G=a=eO{jLpB#ixwf5=L4Fm!X z@IwP`_OSiO7tmuNe8rA^aWOIc{QL_esgdS>Gqvq`pTDlj(sR!TN;<*|2n(>`BvjQX z=i>JoPY&Chhe$n7Kg6!4x{JmKYQwKRpe}()rOF&% zcb~%~aL$}Oxk$sH)m%Tn71a#hfAPhqUN?gOL_R*EFbl~V)fEi{2o}r--q+*!coX+n zP>_xAI^kIJg)G$3_yZ5I5?=uL#bK-=@~R-J-_q@(Hj#VK{=M+U33<3F_WC4 z8R9Y80^&LdiAwI@zd;lL)9$5{bOR9>Gmv9#65TB!0irRanwnZU1dwfjEd!gJgQ)z? zr8trN{CwbKPCORy44}G~0Vvgypob)vY8n+t2|xs(88D{;`8EkE@=b2vBjJ_wpCTn<+mC)^6G~NsPGJGUpE|4iU~G z9v-O!Z3kZq)*rL>3M1BR3{LRCMQsTR!$LM%MeEvQgpry77v=)u0&5FJp{>28GE=uLZj!T4WM42`GDV8LsJAaO2_jm zo-UMSfu^HTq3=d3h}~dO1i=wVCH+7@2J`GKS6bLA`Ol3YP+x7mmR{Py?ON zci(+iSLmqxTq|R&UhKiQB_tQ?cBRo(z{yZ3PQ$mROO5K*H7EV6)YR_aGltD1B~_T9 z(;`-QVhGzI0sXy8i8pNr?^(&DlX| zu>}grv(?i{>7uLic!!i$9}5VVm6b!B;!LoG*ok7>_G{n*-|)39NXpVmlMi77l0#u9 z!hk7diEa5J`hp7I7<%Hu?@^fvdL_`!6njh>8LZ71C>FB$$#(nGIQ%x4EssScjrO0` z);7<-(uKOFhB7u*zWnKz($cyD6n%WmU{vfxIPs;DF9}-(YOUzl*n^Cyn9#7$6OlU@ zbnE1xbB5U9j@+INLh=K=1ok{@4d-XJ!A^~l=G0Ck2~9~}Ka6rc74!M=G`5w365KsjJrylaA}`E11mp& z70L3?jY~Fg@NK(-)3*EgwkBi~VTnrZ>WcsI!CO5~^s84#fgL3>ma;`;mNNA-!g`9v zUwAYc6`AU`i@COG;sA>QOjKvgf$|bM76iyxiVoJDRbGUtWL|O9Y5J5>12o4>4#{39 zmdo_`DPnF>4_};?_J%A1({o$}<-gcp&1ABAiifUUyL#9uz!4^|<&`Vy7Z(FgotmkS zYS6p{H0M(75U+^3AU}W1{Y6}!alk@iiL09{Y_ct_Mrr9RUpfd$fTb+WHbg;iCfR@K z6c92GkB%lGl~z!7(SiWgBtA}H&c{0mDgyOTu%^$Z15~nU%WS^AFwDPoD=1hQB2?mN zM-`q!e8BAOO?ZEg21l2i(yX24*}>NBztpj^Y{ZZpW-Exu;uM|(nfM(>^VQ4HDMAq* zM4a3hf-NVpeXkmqd{Ltj7WhHd?LbVZvLEHw+L^Gf$B=cdP4$@EyUdxzg5#$>GrS+l*ohK++gAxdIN7CrsO210SGI-`Ex)Sf-zsT-k zag5Ox7FTBxHIP|mybDL+6^NtLXo^MGX4!>vKe~xiB zG}38vRdg!7j66IvDaCnIHB9rX5<6c|{j?g;5*lw|YpXabRA?(4it!g*1#yt62BYK0 zgJ6K~9s9AJ?cwHOsBxu5!FM4%JUrJz9Mnc-WlGIGKc(-^_~8e^w~bvpH7<4L_1Tp{ z04xRc+T5_~4rMsfU=7CQVQYWi)U-(yuH?83E`m0`zeY=%&MfGju#xi2k{@)tP}KX@ z*sS^M$n# z2o07RBj2y9W!$Nh4lK@VDF&DMPZ&oXIXHOYo}l~mQ7bDy*`o6c{lVsQo3 zuVDVeSau0tHT-g_rKpEDqp!%4F)xEVLjhdeZC|H{0?yikd+!r47d7~dg;%39s2p~k z9hiyF3@Q>LT+$}xpMl!7x$4>lC(L)D2^ZY{$P|pNHw69nWmU~jX zn~x{L+iVVh;U><9{1&2nV3r#g8bZXmc!53RSnQ2h^~EbYlDR%633bE8NQ?)O5Y!Pc zq|)xJ41#>NuhY(Bh)+3(K<{%0@j9LSo71{XFjly9f8oZ`E&dNA+AP;)x$s|eIJwm4 ze+mfwZ^QvNm!yDyo`N7}I;7ydJ>}Ce+K0)~X_HT`?;L2WtQjBlWuIaBDNX&A1Zh_( zb!(fu0YyhY-j~+K>bzYrNnP^?oz^nd;F3is}J2JtHWepM<2_@O53SQz^*gC=~oZhAu1+`6vng-+Y{ ze|!bwd>O#oDyZ*!`F|=E)Av_5@3-qy%ALZEH{E0GX&=70_-2R5?^JeNlUxRSe7*Eg z=jdL|XJf1&owlkdIPCyr&DEzHqbR~>CB~-Itm}lHVu`F@u*9S!;^0=DakURR6klQ= z{dYpD#777%ZS9+iEIyx8pra7b+Jj3H*OWz@QJ-BlC|s$?JZn{aewU|Mu(*?O!(f<--4a*1s$)TArSNF8l>Ww0^K% z6M=u0GcnR|?8l6$c3q-f;toqMpUBSVg87&dl*E{j`?X5hRsX`ED`)lVM9i+X=PH{L zi51_q<16$M>kq2>PT_9&;HNKO|9RV=c`@8})7g0HrGW@V@rDmY&3$%i#Qj-TV?|Td zi{Fj*pWl^Wu~4?odWQ8}r)|FN8mI~{mZtLmVNx$Qo;#`ebQo3lbckm0Izx$%#|9Ih zjm7CJU5VKA>pd%bh3krO?+I*mB_wZg56Rr(j?OAa8$Ip5_wk)TyS}%U4B}s$f91X3 zcw%ompQi*tZ~6F}y7i%H1>})Z-|fG95BKrQEm|)8{}%l_+4PF}zr1v<*m;&mcV++o z*~V6M_x}^UtVsLs*Co2BJqKd@s-Jd^yejl$uf!tbuaJd}hflOpp&}Uw)=A9ew zlv#2E1rr>0n=^Eifu=ZuY(q2G(AL)e!n4dIe?rn+nve6JO;%Uie0RTb;dQ{m zfVz_oSX9?XNk+Oog7cvV;5En^K?5SqHo|NY1$tZoEWD`eG9cQiVXjsVKw=RHFCmqZ zALVy&>9Vb@Z7uU-m9J+Jp=z?Y2l9VQ0d7N<7NqPxgT$Z4IJq4=b{NDY{0%JT^S>LK z1boSp=xq&&oPdkzfXP#Ng_Un)$_s1&HDu&z0$_4sR*I_VQ-=e<+#rwE4@8kj4xIq` zP5M|SfOJisWY7lwnfA8JA^(ja(Zlkqg2V${LE=P_NJ_Qb$Vw=@y=p%7=$&tyleHoQ zyP&JMg9!Kx8XLx*&&tjNkz2Sl5@H6B`~nE zC~}}ez-yVKy6Bz$(?H63MStMu*W%FOCJaDHa#JD(yJX8Fx@XTbm_hB>56SGgjO%*P z?k|kkH|=cP0SgU`c~u^EX^c)KE7)~Rz?2#z;n4q_GzT4fOTnIk_{`BP8ss-CNlHoz z(vKaR3jwGGJlvtj@Z!bo8r-zmxtZB)_4yWW))a{3^p5*C&o{2E_a`+4~xx8EiRU^Rp*&O*qbzP@~EESpkEZ5oNauO;mLIm*cVdFfSC z(_RQ!>{IiHAqKSWfAr{UJq1{nc-*O=eavWBWRN&X5SRg5IDh^nr0GL!f(h{wNzNn~ zl~eiXvo-6t0RsgnBxPQ?2zk$#Zeh_tuBux~N-FRG)EeS4*2(?=wyHz};Y$OuTi%R; z(9W+vh##Uq7cO3WlAMf#2pb3@V-$LwHIE*>MOTAkNE_+bwBN!rD&RBz+pRER7ccX= zkA_C|^R8H9tl{pKPxC5MIfu=B68oMPoCRnq;@R#s$bC=~pOsYzn)<%g7nbH{)-;^` zD@ZYQQ^hdkI?gdN1UbWrN8D{Kxe`p;Kl6JlgS(MKiwYQ>aL~qBrwy_M@`3Nhn5iFt ztTM=A#5H=;VXe_JK6LP)P20%9NtGKvNvRc%HAGPwIe5_gd?CLqCrp#Dvi&ir<;!%` zd*6jgK8=FmM!cCETY$89LPw_w&<2RJNKY6g+LHGooesISMUhd?7P~ilk2jWO8~H;9 zZr`)X4=G}sH*aY*l17g=Dnt|l$hy9SxuzbQoQ$Hi>z_X_p<6cDsfz?PlQG-)PYK9b z{wZl`pYsRv99xU~z+l=x5Pbc#tlkI(3T$qb7KYzYAm7Ja|ZEkAl zQOYbb%)1!<_%VEG!b{*qSkRG=t%u;^Ux-1o%xbuJS-R4Fe3%`x@ne;pHO$Krta}&9 zJh2MU*H%=ap8+g8JCyC;h$OKl8SB{(V)L++-;9DpGyXIz1m)`E(8vnVeX)7m_;7PVL_8SVr@Otl~sg{pqqE*R{OQ z+0LuE^k}TllUU#Q4#rwr7xf^zW8!335v;V3#k!stkGgQ76y^>3y>Euef-nfJr)z>B z^bY(lcE>mbw7#0(TV8FLYYcoI#~K!Z)abX3jhC=~HbDlHU(|tL(vj^7f}+gTU}uWa zW#gJLWouV5B^fQF(xPpxw^qXQ7)iP|Q7 zqxL@ZX|0G%ca?8vZK4d=*646!*H+XV$TOO032643lCxE;(APChY9+Pt=;R6~3NciY zgRk^#QlT)%HfdjSb=9W2ne1nhZ;R!o1^2^A)w4+DqB}y;!J`(|sYU1ZC_&{L*Bhc6 zL1hC=vln#ZlDi$~*F8s-K#s5%BFGiW*t?ykDxwM!)-w`eI*kpq32&P#=UdjUCn2eu z=0Cc0LT=IE<8Vk-3G_KUdD-6H6n1-=^32wlQAypduwpxsMSIMsHSvKXslPHz+1(gx z|6Bpi9k070L$SUM4#3&LeFDiqk1xlo^TG}MXt|TFZR@i>5}7TPTD}*Q(XV1KfY(0N zRd5EMhE?o5ia;o4=dkv%3eT!60xeetTI?LXUG>HpMSU4HAaMXLK`{GY_tOemcILxJ37_$#+7;^Xrszg?vpG_)mtlbW{)^eu05hy?>QGar^ePV z6K-l+;Qa&G-Bmijnum6+Deznf*<4Yd{5Z3LdMn}Hj8cnyTTs~{Q_X+;L3DWA4tet& zdH4(uez9+%+s_jx?H7qm2ps=$Y%rjGXy*C)&Rc@x0jpNtb@Nj4M!*@(b|T~f3{@lpv_7< zp2F>I*Lrm776TtSn>D^uoXWfV2a)ZUre|((`(BVxv@yfZG4Rn$-%^1C@jHCgl#{?8 z*ep*T3$kzClTW=RrBs6W{dm8AGNQqi$oA9 z!ls(8<$2{B6mC)-n(_$lyy7ZG=-8x1}{Y1wKM)5TjxG-nNlwo{;$W?uikzC k->Y<7f&V_`&^W8}=&DxzHDe!Oi9wt?u5&C;$6?-eIqR%B=dSzV=0mdbB+q{KfA8P^?e^?te!JmIWA*#_nl9jB)07rb@g41i zCrQ8jbRsm2_(;BM^S6sa-TM?DTS;{O#*B1N(2}ORry9~7q|hfiUZ{5v_9_?l~f_RAB?wU0l4f?RyNb?w`s=Cw1d|FpAO zVeQ(wj6Z+cC}V^8br~DQugmyn@rqXtN4X7dg5Zo*78Y_ayssfL@Z6h!RQk;|!ytuj zW6rcfjZ2v)6%;hN!_f%;v6R$eiV<|-1{%#z(hZkFL*5-XF)*!QlUDo}#=R&|j^@f_ zf#kUd_edt&Ay4EB7RUH2Y$XK+qMwnUA2-{!fXDkNcuSNEz7 z6%Ww-`bg;H+cS2JkG2ZIQ43wxu2m{fXw~xj$;ru?p?blX5yq<0cLp5ipx!+uM6JM&Y=E!en0=a^YLFoDrHkg1v(uTHN7`v;-^U(=4l^a^4~x0dz^Z ziY3i9%B%K!npn{gqthj19;?)SXwga7+}5ir&XwfXVW7%7O-P?GY4blP0y&EyWZWQR zgqBWM3pu?wbXeVM+>>3!F^oIs{I&Y25sWuKf<+8GJYaTv(}lu9`2s#SQQ-Hz?LDUt zNePib`z~~u3nm?4^dcv!d`{)7z`{HAFMRsFs9VG|bKQqNxuPbl8#?X3e7Gzg6%{oz zT_eUB@0w`Wm%%GL7*2F&=|>(!F;JZT0Cp9b+v1vP5a$hU_F@m9>caMlyN|{?q^6|2 z!D2ZRR9D{ef_PM19KFDSBA4KMcyS@`tF%vFdzw+AzZPt=x4o?n1)EFRCRk>R@mEhKC`jdgW(b?(~R^C^N=qu+k}p&k(*e0Kmx8;Jg2QS0%~n4Z zk~56AKsl@|vSd*6?auUK-h3L2H`}CC|NOvQM&o+I$%m{Kf*ZQ}I-@x?puq zLeXlz)5J)(_SLUf;TYb6ak8E%7SUhfBLhRPHpeQ<-Ue=WzsY}qCZ-cc)ywxmEm@DlKO7{LldUnfxu^`)aD z%^=Q9Fg<`?VX^2Xa)KT@@`d_%I(Xz>2?KV#{E6q4%Iw2sPAT6^0&DRFu~Aofbn+KOWZ)4-e18d?-jtx;@hPrv6~SgW1tg zaoOSry&x7}kIR{xnnITbA3HiaVr@JcfaYQ0;VT?=Vqm&aqShRfk_f|~86;FRM|l6K zoAocGBfe-mfXCO>)isb|tJ5`irK2mk0Gqlxz>(3aN@c^Zs;jG$l9J}hI4zX_;i&J_ z97O&{8aO`MS+(wnQ)1jU2u`W%Mhzk3fU-SsNoqNcR_^80B9d~x=86I&bf2$Xw-I_a z`q^$d5UkN4um*&bDyJt4C@#JEDa-9ZHi1AGkE`%z5%?mHPTH1N0=j7sYR&u=wtM1B z%2hkN5g;~LR2m1uCm{h%b4*)1;>gIzP<kx41kvQ!ldySRV$;2N-+} z5rD#Ql2_dS!l_lM+t=J%H77|+-)_|e^J-wLH49V(>{j)Y@yI1`F`Q>*oo{xPuYp>g zkCN3-KrfGh7w`a}xQz7lWAgG7z-I35GneJ?^KIHS3J?OMF-(e(~+fOUHvk?61#rbccI5CxPshb|AYO8eRzOoG zqludqvPl}ww7;z&jD#ETIBx1jo&J?Z+@)9Ven&+ zki^NpygT|IR=Ice^gaa{bpu+Q9JC6sD1UxnaP+Wt27roH2pd{gLCT{QDTnEjw5|+X`v_ zLbA6oHT9avDp~Us)dpz*>JX5F`8{_cd9zsZHM?Fw+6=cEku`m%zt#YoSi|9@+jU|8 z*nPBe=96R>B~}ZyZ0pl+YHC_^x8c<=v?)drJg=#tA){BXycMxb&49pG zQdBfR-<8-tJ(S8=8Q{2(d5b{O@bECvwqh}nKbH6^F;K5IM$vk4@eusPowsKluLGjk zYi=|1af{fVy?c2pWWh=b7ES~8%Z;@pZ$ik}lbJz7uMWwud-Cpp?5L)u2EZM6-u)Zi zXTXwI!vaW{1Y`=l(+YRIl-lZMLaZM(bIDiZv$-Q-thSyl)iUIYkPKFF++#WdU&t<|%Qhn5JR zeiuAiDWavG5`(l|JUdg;ucu@t<|ZuiwkJN`OqL9##cqSNTiogvccvX|e2{JnuXz5v z`Kiz-hR=0qx&09W34DsX$ntr32V@(+(XlkCE0FZ#vOn{Wo6wDu$-Bi>2LdOWl(Z05 zX8XjU{t4&Y1_z0gM7)L%Y5v=sB#eLT@EPR-l6!j{UL$d*IKbxOq@}Vffk2JMC} zAztcJO9Vorsl53O1|lFsUhhR%sH7{&u9X-IfY8$91pAgRPm^jt8WrQqD>66BvT1++ zZpe$u@THJ>AM*3h945GSs|~KX2$wg+U%`s$L>PU{uHb(vb8VgK<_$MD6S45sj+uI& zsczQr^XBJRcp!R#@Fswc9jIEFmppX}m>Cd?(&|_wNO$7KOngzjlsG7n7^S}7!EMjd zt!4%)9K_V+%7hju*7(e@Q03O|{Uw5jMB-#pkrUjHTL(a{tK;v#tj0{h%pOlm{q>7C zYd}SiRpzQ>yGxuZ7#vNxDxqXEZRXMIM1oC$TxVN4n6<3R!#Eh}jc%0?Rhy}Gc~)2` z2J4xgy{gn1=;u5WyQ zadN4>UF6+=9S%1Oh!WkpwP^cX-3#e}=U>YwT;ms+)RlRbfI$v?m!I#ESb?x@JBWM` zvL#**vV$CJ3WqQLv0c)iGc_mR&q-Jp?HKWefw2Ra*r4B-oyuEOn9i;h z;r%j-2P}=Wd>}S~< zEHXh}oel3Wf)x-eC;La9!heQ@YR^j-*1TWssi#jd%$~96%tWN&71P^A+lXc2;axPZ zCc+!}MYEvE({I%Z9J&wIq#7i`W|r;%N+CE@1a#(gVWd8v-F#sQcMES%NhouoW`|mo zdFb+^^pEa(MkEs9RfdULtOXFuwiS(71Yn1V{?_hO&?+IR~a%Mhz0>1WOYog}a>TMd6p8myNQ& z<(NNn9VpCL=B`}|UF%z1(cC%-l_|!=^~=&PSZ;NnT``MeX`t{E2fPAxdJ0^$ZSvwQ zbwvJze5PQ))>vHJ(b0?D)QU$K@>$7`Th-WobFE5N6^jv6gyQkz8Eba!JDFpvle9@W zx1>J@-c{^ujYJ}4lWZ%zpXuBN-A-52KG%VGs+Q8>D_6X;EvuGan*@-6hfVzF<5xx} z_sGGxyX{r4`y%RAh-ls@9Nj4>rstlRxzKGx!A>sv?Dy(c)JL(L0<1q| z|IvvmHRX6dZoaJ{Uu-jJ;GVp+O1_tv|?R4AZ^gaJYTMb4Mm(poxBz;Thd) zrqh4b?(SW^h}Pg5eVgU!$K+*DY_BPCxxZXr$Ik9YPaN~+jjj$Fjo#w@o(Ne9de*l8pdv4UA!4S%t zi@b)uVIk0)uyp%WsM-n@RL`}N-9F93r~|iIwwVIW3`{-1RQt%2Y-#eqSdlq`R_sN$ z!(t|$WZQzYf}S1T^iVsa3@ft|NO!)lhP-UT{leQtt>q&HMzDVCt=9v10ny*4WCoWu z{Sw&XCe|>0OioU2ob@r^n}s9sw(01YnRS15jhN~jTM`Hrcu(1ov+uRNLq?mobH?bf zHz|h?YiP(Hj#|BR!tc13l@+Zx%Ni8e_|?r;pp2oYXEGR*_pSU-Y#Csn)JA(mRU^o2 zRdiwDFwbkk!>TAORGm#kHjBgsshhtEyw|42{J|`fo3^yV^L>qVCTXT5-9wyyo3!(PDhy_x1Unaoc@fVf+(;xC*0qMc`L;NAg z&g|x}Dmb%}+hL?t_BGWS!>M(h@S`Cy@#+tjLT5^@rH2tlfVbg=xueMx{><1_JsULr ziN=+ySA>MH(wIPIm15WXcklc&K%v^2pfZHZqsC13aR&mLWRpxY%{06x-!9LyLjs4s z1v!YF00FCDiVs(v)AWp3h70%S4*aP+foO3v7B4O7Ou-mGHx zx3!EJDeYsMzrAfTwgY?8*2eaxqLNKF=3}oIkAvwdh?Dc07|3BfY>0#{m(;tAx81__ zdC;FUhBYQ7sdVsH^$ZOSJqU^Ms}@kntg6BGda3BZdmX8iH2$r|H|7z&Gj%VZq#1K& zcGtoCE-o%7?pTn~s0wdCU0tVF3>^!QWZMRjsjE}}Q2GgSR>h!fH9b3j978uiQO6b3 ze$E}$Q#9ictrsmbQqKqb7h6N~I?}4z-zonWMAYHgqenwlqX`I)u@M_jN}i3f zQC_4;Bw|kbvHVM>@c@i5z?1(ubsYnQe*k;ox{5(!n#R|<4q$Y=*Lo!-_d`KoF7iBXs@*j>oeq4-A_RZ zeXQXG`<;+`L1tOU9=u)vodu-phE(Gz`NOFKuI_dS>edFl;|8#u6=C_RoUf&KJl}|npQA8lzPo!Kv zva7|#y2U-g#C>`+Z7lL-%ryDkA{TiKTRm|L=@+sPCbHJ={oBQasy2=k>}9Qd@#DlH z@YL)d8;XDFg>@a;T3%}RatPj3MJc=Z5Cf*>HM=Lmx&S3PFbOBtd3}vXmWa*QUROY{bikUCM>;_P)ONmQi@m z=bPIyb~=E?%Gbsp3zZGakbgQ!KbVjneP`)t;c%UI4S>7)&A;8!lpomKeocL5tgeha zm+W)MxBuRY96^Lvr|3wrXe6gsTMcG_c~aPH9;aQl2QC)Zq(L;FT<_QaZl~iC>~1lC zM#Hzig+y!Ju{|eF+eGpfQnOL&xu7XSDm)nfRm8erE_ck6uBHiho;Q42Q$fxWt$b)M zxvuly7g2^;EWMqnhJd zjgQ*=cgEPb?ppWG`#6BM_G*!+pI?lY?HWP8Rj)k5vFL?d*5`@{VQ<^EI3|2y{iv9SJ+H_HF{(GK%r Z`}uo?KfPAaTuWk?bq#g0FW!9g{{R~Q5~BbB literal 7788 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 bqYH%gJ?JW;r0V)@tmur1rE$eC7jOIzyu&tn diff --git a/docs/assets/ledmatrix-stocks/display-mode.png b/docs/assets/ledmatrix-stocks/display-mode.png index 3e800fc25522b0e92fda1a7383ce07e76c6f528a..a7f642fe310a857d8174e4df6b36ccb8e7eaf60d 100644 GIT binary patch literal 14741 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 HeDuEn%8|q2 literal 14462 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⁣}#Rdij#>S4kT(yL3 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 MLQXvG=7T@~AN~ECAOHXW diff --git a/docs/assets/ledmatrix-stocks/gain-loss.png b/docs/assets/ledmatrix-stocks/gain-loss.png index 0b995880290c5e400b2e4bdb8b5a208ecf51a4e9..ff14860bbfb66f1bbd7c54615fc770b0b41c0538 100644 GIT binary patch literal 19032 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 NLdQTm=f_{J{V%p0rtts( literal 19203 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 A9{>OV literal 17017 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 a&UaHh$ak}|;n diff --git a/docs/assets/ledmatrix-stocks/toggle-chart.png b/docs/assets/ledmatrix-stocks/toggle-chart.png index 71c159d7c07e4e9739a181ac2ea1f861263f4e13..5b8ab1b93b5b01bd3f2a17de8c5872378fc3a467 100644 GIT binary patch literal 9492 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 literal 8289 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 R{MP}<`Ez<_Q#Efq_%A)Es%HQI diff --git a/docs/plugin-development/01-plugin-anatomy.md b/docs/plugin-development/01-plugin-anatomy.md index 364f1e3b..ee2718ad 100644 --- a/docs/plugin-development/01-plugin-anatomy.md +++ b/docs/plugin-development/01-plugin-anatomy.md @@ -61,8 +61,9 @@ draw here. ## The lifecycle methods -The core calls six methods on your plugin. Only `__init__` is strictly -mandatory, but a useful plugin implements at least `update` and `display`. +The core calls six methods on your plugin. `update` and `display` are abstract +on `BasePlugin`, so every plugin must implement them (a class missing either +cannot be instantiated); the rest have working base implementations. | Method | Signature | When the core calls it | Rule | |--------|-----------|------------------------|------| @@ -110,8 +111,9 @@ def display(self, force_clear=False): if force_clear: self.display_manager.clear() w, h = self.display_manager.width, self.display_manager.height + # x is the left edge unless centered=True; y is the top of the text self.display_manager.draw_text(self.message, x=w // 2, y=h // 2, - color=self.color, font=self.bdf_font) + color=self.color, font=self.bdf_font, centered=True) self.display_manager.update_display() ``` @@ -219,7 +221,8 @@ class MyPlugin(BasePlugin): if force_clear: self.display_manager.clear() w, h = self.display_manager.width, self.display_manager.height - self.display_manager.draw_text(self.message, x=w // 2, y=h // 2, color=self.color) + self.display_manager.draw_text(self.message, x=w // 2, y=h // 2, + color=self.color, centered=True) self.display_manager.update_display() def validate_config(self): diff --git a/docs/plugin-development/02-core-api.md b/docs/plugin-development/02-core-api.md index 68b0a2a8..494b7e4f 100644 --- a/docs/plugin-development/02-core-api.md +++ b/docs/plugin-development/02-core-api.md @@ -15,10 +15,13 @@ what each provides. | `self.cache_manager` | Shared network/data cache — use it for anything fetched | | `self.plugin_manager` | Access to shared services: `font_manager`, `config_manager`, other plugins | -The base class also derives some convenience values from config that most plugins -read directly: `self.enabled` (default `True`), `self.display_duration`, -`self.update_interval`, and `self.global_config` (the `global` config block, -where cross-cutting settings like `target_fps` live). +The base class also sets `self.enabled` (from config, default `True`) and +exposes `self.global_config` — the **whole** LEDMatrix config, read-only, where +device-wide settings like `target_fps` and `timezone` live (`{}` when +unavailable). It does **not** set `self.display_duration` or +`self.update_interval`: read those from `self.config` yourself (the core asks +through `get_display_duration()` / `get_update_interval()`, which you can +override). ## `self.logger` @@ -59,7 +62,7 @@ The drawing surface. The most common members: | `.small_font` / `.extra_small_font` / `.regular_font` | Pre-loaded fonts you can use without registering your own | Scrolling plugins additionally coordinate with the core loop through -`.set_scrolling_state(...)`, `.is_currently_scrolling`, `.defer_update(...)`, and +`.set_scrolling_state(...)`, `.is_currently_scrolling()`, `.defer_update(...)`, and `.process_deferred_updates()` — see [topic 3](./03-advanced-features.md#high-fps--smooth-scrolling). Two rendering styles coexist: draw with `draw_text` for simple text (like @@ -77,9 +80,9 @@ and multiple plugins don't re-hit APIs. Fetch in `update()`, never in |--------|---------| | `get(key, max_age=)` | Return the cached value, or `None` if missing/older than `max_age` | | `set(key, value, ttl=)` | Store a value with an optional time-to-live | -| `get_cached_data_with_strategy(key, strategy)` | Strategy-driven read (e.g. `'leaderboard'`) that layers a TTL/refresh policy on top of raw get/set | +| `get_cached_data_with_strategy(key, data_type)` | Strategy-driven read (e.g. `'leaderboard'`) that layers a TTL/refresh policy on top of raw get/set | | `save_cache(key, data)` | Strategy-driven write partner to the above | -| `get_with_auto_strategy(...)` | Auto-selected strategy variant | +| `get_with_auto_strategy(key)` | Same, with the data type inferred from the key | | `delete(key)` / `clear_cache()` | Invalidate one key / everything | **Always namespace keys with your plugin id** so they never collide with another @@ -137,10 +140,15 @@ fm.register_manager_font( **Fetch in `display()`:** ```python -message_font = fm.get_font(f"{self.plugin_id}.message") +message_font = fm.resolve_font(f"{self.plugin_id}.message", "press_start", 10, + plugin_id=self.plugin_id) self.display_manager.draw_text(self.message, x=..., y=..., font=message_font) ``` +`resolve_font(element_key, family, size_px, plugin_id=None)` applies any user +override for that element, then loads the font; `get_font(family, size_px)` +takes no element key and skips overrides. + The `element_key` convention is `f"{self.plugin_id}."` — prefixing with the plugin id avoids collisions with other plugins' registered fonts. Common families seen across plugins include `press_start` and `four_by_six`. Provide a diff --git a/docs/plugin-development/03-advanced-features.md b/docs/plugin-development/03-advanced-features.md index 64740256..15e30f60 100644 --- a/docs/plugin-development/03-advanced-features.md +++ b/docs/plugin-development/03-advanced-features.md @@ -18,18 +18,19 @@ so your plugin still loads on an older core. By default the core shows a plugin for a fixed `display_duration`. Dynamic duration lets a plugin say "hold my screen for a computed time instead" — long -enough to finish a scroll, or to keep a live game up. You opt in by implementing -`supports_dynamic_duration()`; the rest of the family lets the core size and end -the turn. +enough to finish a scroll, or to keep a live game up. The core asks +`supports_dynamic_duration()`; `BasePlugin`'s version returns the config's +`dynamic_duration.enabled`, so override it only for custom logic. The rest of the +family lets the core size and end the turn. | Hook | Signature | Role | |------|-----------|------| | `supports_dynamic_duration` | `(self) -> bool` (or `(self, mode_type=None)`) | Gate — return `True` to opt in | | `get_display_duration` | `(self) -> float` | The computed seconds to display | -| `get_cycle_duration` | `(self, display_mode=None) -> Optional[float]` | Per-mode duration for one full cycle | -| `get_dynamic_duration` | `(self) -> int` | Simple computed duration (alt to the above) | -| `get_dynamic_duration_cap` | `(self) -> Optional[float]` | Upper bound the core will wait | -| `get_dynamic_duration_floor` | `(self) -> Optional[float]` | Lower bound | +| `get_cycle_duration` | `(self, display_mode=None) -> Optional[float]` (called with `display_mode=` as a keyword) | Per-mode duration for one full cycle | +| `get_dynamic_duration` | `(self) -> int` | Plugin-internal helper some plugins define — the core does not call it | +| `get_dynamic_duration_cap` | `(self) -> Optional[float]` | Upper bound the core will wait (base reads `dynamic_duration.max_duration_seconds`) | +| `get_dynamic_duration_floor` | `(self) -> Optional[float]` | Plugin-internal helper — the core does not call it | | `is_cycle_complete` | `(self) -> bool` | Tell the core the scroll/animation finished | | `reset_cycle_state` | `(self) -> None` | Reset between turns (call `super()` if you override) | @@ -105,6 +106,13 @@ There are three mechanisms, used together: [`plugins/christmas-countdown/config_schema.json`](../../plugins/christmas-countdown/config_schema.json) switches 120 FPS transitions vs. 30 FPS. +None of this matters unless the core runs your plugin in its high-FPS loop +(8 ms ticks). It does that only for a plugin that declares `needs_high_fps` +(an attribute or property, read when the mode starts) or has a truthy +`enable_scrolling`; every other plugin gets **one `display()` call per +second**, so a sub-second animation aliases. Declare `needs_high_fps = True` +when the screen genuinely moves. + Scrolling plugins also coordinate with the loop through the display manager's `set_scrolling_state`, `defer_update`, and `process_deferred_updates` so the core knows a scroll is in progress. @@ -143,19 +151,23 @@ endlessly-scrolling strip. A plugin opts in by implementing: | Hook | Signature | Role | |------|-----------|------| -| `get_vegas_content` | `(self) -> Optional[list[Image]]` | The PIL image(s) to splice into the strip, or `None` | -| `get_vegas_content_type` | `(self) -> str` | `'single'` or `'multi'` (multiple scrollable items, e.g. games) | +| `get_vegas_content` | `(self) -> Optional[Image \| list[Image]]` | The PIL image(s) to splice into the strip, or `None` | +| `get_vegas_content_type` | `(self) -> str` | `'multi'` (multiple scrollable items, e.g. games), `'static'` (one block; the base default) or `'none'` (excluded) | | `get_vegas_display_mode` | `(self) -> VegasDisplayMode` | How this plugin behaves in the strip | Import the enum defensively — older cores don't ship it: ```python +from src.plugin_system.base_plugin import BasePlugin try: - from src.plugin_system.base_plugin import BasePlugin, VegasDisplayMode + from src.plugin_system.base_plugin import VegasDisplayMode except ImportError: VegasDisplayMode = None ``` +(Keep `BasePlugin` out of the `try` — a combined import that fails leaves +`BasePlugin` undefined too.) + The `vegas_mode` config key (mark it `x-advanced`) overrides the display mode and is an enum: @@ -163,6 +175,9 @@ is an enum: - `fixed` — the whole display scrolls by as one block - `static` — the marquee pauses while the plugin shows for its duration +Only `static` changes what Vegas does: `scroll` and `fixed` both join the strip +(Vegas never told them apart, and core 3.9.0 drops the distinction). + `get_vegas_display_mode` should honor the config override, falling back to the plugin's natural default: @@ -186,7 +201,8 @@ and the `vegas_mode` config declarations in and [`plugins/olympics/config_schema.json`](../../plugins/olympics/config_schema.json). Core 3.8.0 adds `vegas_participation` (`"scroll"`, `"pause"` or `"exclude"`) in the manifest as the way to declare a fixed answer; the hooks above still -decide it when the manifest and the user's config say nothing, and only +decide it when the manifest and the user's config say nothing (override +`get_vegas_participation()` only when the answer depends on state), and only `STATIC` (pauses) and content type `'none'` (excluded) change anything. Do not implement `get_supported_vegas_modes`: core never read it and removes it in 3.9.0. See the core's `docs/PLUGIN_API_REFERENCE.md`, "Vegas participation". diff --git a/docs/plugin-development/04-styling-and-skins.md b/docs/plugin-development/04-styling-and-skins.md index cbc7532a..522bf31d 100644 --- a/docs/plugin-development/04-styling-and-skins.md +++ b/docs/plugin-development/04-styling-and-skins.md @@ -15,7 +15,7 @@ extensions that shape the config form itself. The older, explicit form: a `customization` object in `config_schema.json` with one sub-object per text element, each declaring `font`, `font_size`, and -`text_color`. About 17 plugins use a `customization` block. The canonical +`text_color`. About two dozen plugins use a `customization` block. The canonical per-element shape (from [`plugins/clock-simple/config_schema.json`](../../plugins/clock-simple/config_schema.json), element `time_text`): @@ -36,15 +36,18 @@ element `time_text`): Your code then reads `config["customization"]["time_text"]["font_size"]` etc. and feeds those into font registration / drawing. This is fully self-contained — it -works on any core. +works on any core. (Core 3.4.0+ also recognises a hand-written block of this shape +and renders it with its composite style editor, with no plugin change.) ### B) The `x-style-elements` shorthand (newer, core-assisted) The compact form: instead of hand-writing each element object, you declare a single `x-style-elements` map on the `customization` object, and a **newer core** -expands it into the full per-element style UI and a resolver. Only -[`plugins/of-the-day`](../../plugins/of-the-day/config_schema.json) uses it today -— it's the reference implementation. +expands it into the full per-element style UI and a resolver. +[`plugins/of-the-day`](../../plugins/of-the-day/config_schema.json) is the +reference implementation; `ledmatrix-weather` uses it too. A sibling +`x-style-modes` declaration adds per-display-mode overrides +(`customization.modes.`); the scoreboards use it. ```json "customization": { @@ -107,6 +110,13 @@ title_y = margin_top + title.offset[1] self.display_manager.draw_text(text, x=title_x, y=title_y, color=title.color, font=title.font) ``` +On core 3.4.0+ you don't need to build the resolver yourself: `BasePlugin.styles` +finds the plugin's schema, rebuilds on config change, and never raises +(`self.styles.style("title_text", classic_font=..., classic_size=..., +classic_color=...)`; set the class attribute `STYLE_MODE` for per-mode +overrides). Guard it with `getattr(self, "styles", None)` if your floor is +older. + See [`plugins/of-the-day/manager.py`](../../plugins/of-the-day/manager.py) for the full `_element_styles()` helper, including the `STYLE_AVAILABLE == False` fallback that loads bundled fonts directly with offset `(0, 0)`. @@ -137,13 +147,14 @@ Beyond styling, the web UI honors a family of custom `x-` keys in | `x-columns` | array | Column keys for the `array-table` widget | | `x-placeholder` | string | Input placeholder text | | `x-display` | string (e.g. `"hidden"`) | Field display mode | +| `x-style-elements` / `x-style-modes` | object / array | Compact style declarations on `customization` (see above) | ### `x-widget` values seen in the wild `color-picker` (the common one), `checkbox-group`, `file-upload` / `file-upload-single`, `array-table`, `radio`, `select`, `time-picker`, `date-picker`, `schedule`, `tag-input`, `custom-feeds`, `plugin-file-manager`, -`google-calendar-picker`. +`google-calendar-picker`, `google-oauth`. Examples: `color-picker` in [`plugins/baseball-scoreboard/config_schema.json`](../../plugins/baseball-scoreboard/config_schema.json); diff --git a/docs/plugin-development/05-adaptive-layout.md b/docs/plugin-development/05-adaptive-layout.md index 48d3eb0f..4139bae7 100644 --- a/docs/plugin-development/05-adaptive-layout.md +++ b/docs/plugin-development/05-adaptive-layout.md @@ -137,8 +137,9 @@ vs. "caller should draw its classic layout," so there's always a working path. - **flights `layout` / `widescreen_threshold`** — flights has its own compact/wide selection independent of `layout_mode`. - **`design_size` in the manifest** — declare your layout's reference size (e.g. - `"display": {"design_size": [128, 32]}`) so the harness and adaptive scaling - know the baseline. Opt into strict fill checking via + `"display": {"design_size": {"width": 128, "height": 32}}` — an object, per + the core manifest schema; the default is 128×32) so the harness and adaptive + scaling know the baseline. Opt into strict fill checking via `test/harness.json` `{"fill_check": "strict"}` once your plugin is adaptive. > The core's own `docs/ADAPTIVE_LAYOUT.md` is the authoritative reference for the diff --git a/docs/plugin-development/06-manifest-and-config-schema.md b/docs/plugin-development/06-manifest-and-config-schema.md index 5b2969f9..1557b7bd 100644 --- a/docs/plugin-development/06-manifest-and-config-schema.md +++ b/docs/plugin-development/06-manifest-and-config-schema.md @@ -14,25 +14,32 @@ Validated on every PR against the core repo's `schema/manifest_schema.json`. ### Core required fields +The core schema's `required` list — a manifest missing any of these fails CI: + | Field | Meaning | |-------|---------| | `id` | Plugin identifier — **must match the directory name** | | `name` | Human-readable display name | | `version` | Semver string, e.g. `"1.2.3"` | +| `author` | Plugin author | +| `entry_point` | Python file with the class (every plugin here uses `manager.py`; the loader falls back to it when absent, but the schema requires the field) | | `class_name` | The Python class name in the entry point | +| `compatible_versions` | Array of core-version constraint strings, e.g. `[">=3.8.0"]` — keep it in line with the floor below | + +Not in the schema's `required` list, but needed in practice: + +| Field | Meaning | +|-------|---------| | `display_modes` | Array of the mode names this plugin supports | ### Fields present on essentially every plugin -`author`, `description`, `entry_point` (always `manager.py`), `tags`, `versions` -(the changelog array), `last_updated`, and `compatible_versions` (an array of -constraint strings — almost all use `[">=2.0.0"]`). +`description`, `tags`, `versions` (the changelog array) and `last_updated`. ### Common optional fields | Field | Meaning | |-------|---------| -| `entry_point` | Python file with the class (default `manager.py`) | | `category` | Store category | | `config_schema` | Path to the schema file (the string `"config_schema.json"`) | | `icon`, `homepage`, `license` | Store display / links | @@ -68,8 +75,9 @@ usually a minimum-core-version key and a description: > > Note the **name is inverted** between the two locations — `min_ledmatrix_version` > at the top level, `ledmatrix_min_version` inside `versions[]` — which is easy to -> read past. Four plugins declare the top-level form today (`ledmatrix-flights`, -> `ledmatrix-leaderboard`, `ledmatrix-music`, `ledmatrix-stocks`), and for those +> read past. Five plugins declare the top-level form today (`ledmatrix-flights`, +> `ledmatrix-leaderboard`, `ledmatrix-music`, `ledmatrix-stocks`, +> `nfl-stat-leaders`), and for those > **editing `versions[0]` changes nothing the core reads.** Check for a top-level > key before raising a floor, and raise the one that actually wins. CI fails a > changed plugin whose floors in these places disagree, so raise them together. @@ -120,7 +128,9 @@ never receive the update — and CI fails the PR. ### Every plugin change: 1. Make your code changes in `plugins//`. -2. **Bump `version`** in `plugins//manifest.json` (semver). +2. **Bump `version`** in `plugins//manifest.json` (semver) and add + a matching new entry at the **top** of `versions[]` — CI requires + `version == versions[0].version` and a new top entry. 3. Commit — the pre-commit hook runs `update_registry.py` and stages the updated `plugins.json` into the same commit. diff --git a/docs/plugin-development/07-testing-ci-and-registry.md b/docs/plugin-development/07-testing-ci-and-registry.md index 7a3831af..4bb8e5f4 100644 --- a/docs/plugin-development/07-testing-ci-and-registry.md +++ b/docs/plugin-development/07-testing-ci-and-registry.md @@ -112,7 +112,7 @@ Four workflows run from `.github/workflows/`: ### `test-plugins.yml` — "Plugin Safety" Triggers on PRs touching `plugins/**`, `scripts/**`, `update_registry.py`, -`plugins.json` or the workflow itself. It checks out the core repo +`plugins.json`, the root `README.md` or the workflow itself. It checks out the core repo (`ChuckBuilds/LEDMatrix@main`, full history with tags) for the harness, the manifest schema and the imports several guards need. @@ -145,6 +145,9 @@ Steps, in order: 6. **Repo-level guard tests** (always) — every `scripts/test_*.py`, discovered by glob, with `LEDMATRIX_CORE` and `PYTHONPATH` pointing at the core checkout. A new guard runs the day it lands; there is no list to update. + This is where `test_readme_lists_every_plugin.py` runs: every + `plugins//` has a row in the root README's Available Plugins tables, and + each `### Category (N)` count matches its rows. 7. **Plugin unit tests** (always, for plugins with any change) — `scripts/run_plugin_tests.py --core ` runs the plugins' own `test_*.py`. @@ -169,9 +172,6 @@ the core. - `test_pixel_perfect_text.py`: text draws are 1-bit (no anti-aliasing) - `update_readme_previews.py --check`: the root README's Preview column matches the `hero.png` files on disk -- `test_readme_lists_every_plugin.py`: every `plugins//` has a row in the - root README's Available Plugins tables, and each `### Category (N)` count - matches its rows - `check_secrets_template.py`: every `x-secret` schema field has a placeholder in the root `config_secrets.template.json`, under the plugin id @@ -315,7 +315,7 @@ Not run by CI; use them by hand. - `scripts/render_docs_assets.py` — renders a plugin README's screenshots from `docs/assets//shots.json` through the core renderer; `--check` diffs against what is committed. `--all --check` is not in CI: it re-renders all - 42 shot lists through the core renderer (each plugin's dependencies needed), + 44 shot lists through the core renderer (each plugin's dependencies needed), and a local run was still rendering, with no output yet, after more than five minutes. Run it for the plugins you touched, e.g. `--plugin --check`. diff --git a/docs/plugin-development/README.md b/docs/plugin-development/README.md index 09eb44ad..aa3945dc 100644 --- a/docs/plugin-development/README.md +++ b/docs/plugin-development/README.md @@ -25,6 +25,7 @@ back to. Then work through these topics in order: | 5 | [Adaptive layout](./05-adaptive-layout.md) | `layout_mode` classic vs. adaptive, and rendering across matrix sizes | | 6 | [Manifest & config schema](./06-manifest-and-config-schema.md) | Manifest fields, `config_schema.json` conventions, and the version workflow | | 7 | [Testing, CI & the registry](./07-testing-ci-and-registry.md) | Safety harness, module-collision checks, `plugins.json`, and the CI gates | +| 8 | [Shared sports code](./08-shared-sports-code.md) | The scoreboards' copied modules, their lineages, what core already ships, and porting a fix across siblings | ## The golden rules diff --git a/plugins.json b/plugins.json index 7ebcaef2..9ad8f6c4 100644 --- a/plugins.json +++ b/plugins.json @@ -24,12 +24,11 @@ "plugin_path": "plugins/cricket-scoreboard", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.3.2", - "ledmatrix_min_version": "2.0.0", - "commit": "e429d8e46b547f09a0bd93af60002db6357a8bf8" + "latest_version": "1.3.3", + "ledmatrix_min_version": "2.0.0" }, { "id": "7-segment-clock", @@ -49,12 +48,11 @@ "plugin_path": "plugins/7-segment-clock", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.0.10", - "ledmatrix_min_version": "2.0.0", - "commit": "62fa95853476715b833abfe0b207d4f50876b6b4" + "latest_version": "1.0.11", + "ledmatrix_min_version": "2.0.0" }, { "id": "baseball-scoreboard", @@ -80,9 +78,8 @@ "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.56.1", - "ledmatrix_min_version": "3.8.0", - "commit": "f9ced936bab32e05df5dd71bcabbc94972aa634b" + "latest_version": "1.57.0", + "ledmatrix_min_version": "3.8.0" }, { "id": "basketball-scoreboard", @@ -107,9 +104,8 @@ "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.41.2", - "ledmatrix_min_version": "3.8.0", - "commit": "f9ced936bab32e05df5dd71bcabbc94972aa634b" + "latest_version": "1.42.0", + "ledmatrix_min_version": "3.8.0" }, { "id": "calendar", @@ -132,9 +128,8 @@ "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.2.10", - "ledmatrix_min_version": "3.3.0", - "commit": "f9ced936bab32e05df5dd71bcabbc94972aa634b" + "latest_version": "1.2.11", + "ledmatrix_min_version": "3.3.0" }, { "id": "christmas-countdown", @@ -153,12 +148,11 @@ "plugin_path": "plugins/christmas-countdown", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "2.0.3", - "ledmatrix_min_version": "2.0.0", - "commit": "d6223d0f6f6299238fa6c952143004a2f45e3c92" + "latest_version": "2.0.4", + "ledmatrix_min_version": "2.0.0" }, { "id": "clock-simple", @@ -176,12 +170,11 @@ "plugin_path": "plugins/clock-simple", "stars": 0, "downloads": 0, - "last_updated": "2026-09-29", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.1.3", - "ledmatrix_min_version": "2.0.0", - "commit": "843588025a81197056f8d96779ccb2be19337ab8" + "latest_version": "1.1.4", + "ledmatrix_min_version": "2.0.0" }, { "id": "countdown", @@ -204,10 +197,9 @@ "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "3.3.4", + "latest_version": "3.3.5", "icon": "fa-clock", - "ledmatrix_min_version": "2.0.0", - "commit": "a6a38144d3544b236032926bf844ad730c825917" + "ledmatrix_min_version": "2.0.0" }, { "id": "f1-scoreboard", @@ -228,12 +220,11 @@ "plugin_path": "plugins/f1-scoreboard", "stars": 0, "downloads": 0, - "last_updated": "2026-09-29", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.10.0", - "ledmatrix_min_version": "3.6.1", - "commit": "30455671d5856814cc972040297341de60b673fa" + "latest_version": "1.10.1", + "ledmatrix_min_version": "3.6.1" }, { "id": "f1-live", @@ -283,9 +274,8 @@ "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "3.18.4", - "ledmatrix_min_version": "3.8.0", - "commit": "f9ced936bab32e05df5dd71bcabbc94972aa634b" + "latest_version": "3.18.5", + "ledmatrix_min_version": "3.8.0" }, { "id": "geochron", @@ -356,12 +346,11 @@ "plugin_path": "plugins/hello-world", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.1.3", - "ledmatrix_min_version": "2.0.0", - "commit": "d6223d0f6f6299238fa6c952143004a2f45e3c92" + "latest_version": "1.1.4", + "ledmatrix_min_version": "2.0.0" }, { "id": "hockey-scoreboard", @@ -385,10 +374,9 @@ "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.42.1", + "latest_version": "1.42.2", "icon": "fas fa-hockey-puck", - "ledmatrix_min_version": "3.8.0", - "commit": "f9ced936bab32e05df5dd71bcabbc94972aa634b" + "ledmatrix_min_version": "3.8.0" }, { "id": "lacrosse-scoreboard", @@ -411,10 +399,9 @@ "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.36.2", + "latest_version": "1.36.3", "icon": "fas fa-baseball-ball", - "ledmatrix_min_version": "3.8.0", - "commit": "f9ced936bab32e05df5dd71bcabbc94972aa634b" + "ledmatrix_min_version": "3.8.0" }, { "id": "birdnet-go", @@ -436,12 +423,11 @@ "plugin_path": "plugins/birdnet-go", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.2.7", - "ledmatrix_min_version": "2.0.0", - "commit": "d6223d0f6f6299238fa6c952143004a2f45e3c92" + "latest_version": "1.2.8", + "ledmatrix_min_version": "2.0.0" }, { "id": "leaderboard", @@ -465,15 +451,14 @@ "plugin_path": "plugins/ledmatrix-leaderboard", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.5.4", + "latest_version": "1.5.5", "ledmatrix_min_version": "3.4.0", "aliases": [ "ledmatrix-leaderboard" - ], - "commit": "62fa95853476715b833abfe0b207d4f50876b6b4" + ] }, { "id": "ledmatrix-flights", @@ -523,12 +508,11 @@ "plugin_path": "plugins/march-madness", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "2.1.3", - "ledmatrix_min_version": "3.4.0", - "commit": "e429d8e46b547f09a0bd93af60002db6357a8bf8" + "latest_version": "2.1.4", + "ledmatrix_min_version": "3.4.0" }, { "id": "masters-tournament", @@ -549,12 +533,11 @@ "plugin_path": "plugins/masters-tournament", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "3.2.4", - "ledmatrix_min_version": "3.4.0", - "commit": "62fa95853476715b833abfe0b207d4f50876b6b4" + "latest_version": "3.2.5", + "ledmatrix_min_version": "3.4.0" }, { "id": "mqtt-notifications", @@ -574,12 +557,11 @@ "plugin_path": "plugins/mqtt-notifications", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.2.7", - "ledmatrix_min_version": "2.0.0", - "commit": "67d6ae4b1f4bc7529cc2fcc485d9f1477e9dea66" + "latest_version": "1.2.8", + "ledmatrix_min_version": "2.0.0" }, { "id": "music", @@ -602,12 +584,11 @@ "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.5.3", + "latest_version": "1.5.4", "ledmatrix_min_version": "2.0.0", "aliases": [ "ledmatrix-music" - ], - "commit": "988f1a9e5c4d037a81ae4ec4bfc7b0aec3fcda21" + ] }, { "id": "news", @@ -628,12 +609,11 @@ "plugin_path": "plugins/news", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.6.4", - "ledmatrix_min_version": "3.4.0", - "commit": "026931a925cddb96c214eb92fe2796f5a33a4b89" + "latest_version": "1.6.5", + "ledmatrix_min_version": "3.4.0" }, { "id": "on-air", @@ -654,13 +634,12 @@ "plugin_path": "plugins/on-air", "stars": 0, "downloads": 0, - "last_updated": "2026-09-29", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.2.13", + "latest_version": "1.2.14", "icon": "fa-circle-dot", - "ledmatrix_min_version": "2.0.0", - "commit": "c695b9761d59a9379222c8abfca41c14297e6cb9" + "ledmatrix_min_version": "2.0.0" }, { "id": "nfl-draft", @@ -680,13 +659,12 @@ "plugin_path": "plugins/nfl-draft", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "2.2.1", + "latest_version": "2.2.2", "icon": "fas fa-football-ball", - "ledmatrix_min_version": "3.4.0", - "commit": "62fa95853476715b833abfe0b207d4f50876b6b4" + "ledmatrix_min_version": "3.4.0" }, { "id": "odds-ticker", @@ -710,12 +688,11 @@ "plugin_path": "plugins/odds-ticker", "stars": 0, "downloads": 0, - "last_updated": "2026-10-01", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.7.6", - "ledmatrix_min_version": "3.4.0", - "commit": "e90b592502f3a20e50fe62a4ebc977d9c24a2d7d" + "latest_version": "1.7.7", + "ledmatrix_min_version": "3.4.0" }, { "id": "of-the-day", @@ -735,12 +712,11 @@ "plugin_path": "plugins/of-the-day", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.4.8", - "ledmatrix_min_version": "2.0.0", - "commit": "e429d8e46b547f09a0bd93af60002db6357a8bf8" + "latest_version": "1.4.9", + "ledmatrix_min_version": "2.0.0" }, { "id": "olympics", @@ -765,9 +741,8 @@ "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "3.1.4", - "ledmatrix_min_version": "2.0.0", - "commit": "f9ced936bab32e05df5dd71bcabbc94972aa634b" + "latest_version": "3.1.5", + "ledmatrix_min_version": "2.0.0" }, { "id": "pga-tour-leaderboard", @@ -844,12 +819,11 @@ "plugin_path": "plugins/soccer-scoreboard", "stars": 0, "downloads": 0, - "last_updated": "2026-10-01", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "2.39.1", - "ledmatrix_min_version": "3.8.0", - "commit": "56e3f7a0070a26bd7b3fcd80b0214445ded480ac" + "latest_version": "2.39.2", + "ledmatrix_min_version": "3.8.0" }, { "id": "static-image", @@ -869,12 +843,11 @@ "plugin_path": "plugins/static-image", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.1.7", - "ledmatrix_min_version": "2.0.0", - "commit": "62fa95853476715b833abfe0b207d4f50876b6b4" + "latest_version": "1.1.8", + "ledmatrix_min_version": "2.0.0" }, { "id": "stock-news", @@ -897,12 +870,11 @@ "plugin_path": "plugins/stock-news", "stars": 0, "downloads": 0, - "last_updated": "2026-10-01", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "2.8.3", - "ledmatrix_min_version": "3.4.0", - "commit": "9826f800aa163713d7927975bb1ddaf0de121751" + "latest_version": "2.8.4", + "ledmatrix_min_version": "3.4.0" }, { "id": "stocks", @@ -922,16 +894,15 @@ "plugin_path": "plugins/ledmatrix-stocks", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "2.11.2", + "latest_version": "2.11.3", "icon": "fas fa-chart-line", "ledmatrix_min_version": "3.4.0", "aliases": [ "ledmatrix-stocks" - ], - "commit": "026931a925cddb96c214eb92fe2796f5a33a4b89" + ] }, { "id": "text-display", @@ -951,12 +922,11 @@ "plugin_path": "plugins/text-display", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.3.2", - "ledmatrix_min_version": "3.4.0", - "commit": "d6223d0f6f6299238fa6c952143004a2f45e3c92" + "latest_version": "1.3.3", + "ledmatrix_min_version": "3.4.0" }, { "id": "tide-display", @@ -977,13 +947,12 @@ "plugin_path": "plugins/tide-display", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.3.4", + "latest_version": "1.3.5", "icon": "fa-water", - "ledmatrix_min_version": "2.0.0", - "commit": "62fa95853476715b833abfe0b207d4f50876b6b4" + "ledmatrix_min_version": "2.0.0" }, { "id": "ufc-scoreboard", @@ -1007,10 +976,9 @@ "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.19.2", + "latest_version": "1.19.3", "icon": "fas fa-fist-raised", - "ledmatrix_min_version": "3.8.0", - "commit": "f9ced936bab32e05df5dd71bcabbc94972aa634b" + "ledmatrix_min_version": "3.8.0" }, { "id": "weather", @@ -1032,17 +1000,16 @@ "repo": "https://github.com/ChuckBuilds/ledmatrix-plugins", "branch": "main", "plugin_path": "plugins/ledmatrix-weather", - "latest_version": "2.8.0", + "latest_version": "2.8.1", "stars": 0, "downloads": 0, - "last_updated": "2026-09-30", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", "ledmatrix_min_version": "3.8.0", "aliases": [ "ledmatrix-weather" - ], - "commit": "555e63f5c79221d206d740bea11698249ce0eb08" + ] }, { "id": "web-ui-info", @@ -1086,12 +1053,11 @@ "plugin_path": "plugins/youtube-stats", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.3.2", - "ledmatrix_min_version": "3.4.0", - "commit": "44618443dd6e2ea581377d589864dcc9190d2114" + "latest_version": "1.3.3", + "ledmatrix_min_version": "3.4.0" }, { "id": "ledmatrix-elections", @@ -1111,12 +1077,11 @@ "plugin_path": "plugins/ledmatrix-elections", "stars": 0, "downloads": 0, - "last_updated": "2026-09-16", + "last_updated": "2026-10-02", "verified": false, "screenshot": "", - "latest_version": "1.3.1", - "ledmatrix_min_version": "3.4.0", - "commit": "912724bccbf6c56985be2b1fcdf16b79a33dd0c2" + "latest_version": "1.3.2", + "ledmatrix_min_version": "3.4.0" }, { "id": "ledmatrix-dresden-departures", @@ -1164,10 +1129,9 @@ "downloads": 0, "verified": true, "screenshot": "", - "latest_version": "1.35.1", - "last_updated": "2026-10-01", - "ledmatrix_min_version": "3.8.0", - "commit": "56e3f7a0070a26bd7b3fcd80b0214445ded480ac" + "latest_version": "1.35.2", + "last_updated": "2026-10-02", + "ledmatrix_min_version": "3.8.0" }, { "id": "tidbyt-baseball-scoreboard", @@ -1210,12 +1174,11 @@ "plugin_path": "plugins/nrl-scoreboard", "stars": 0, "downloads": 0, - "last_updated": "2026-10-01", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.34.1", - "ledmatrix_min_version": "3.8.0", - "commit": "56e3f7a0070a26bd7b3fcd80b0214445ded480ac" + "latest_version": "1.34.2", + "ledmatrix_min_version": "3.8.0" }, { "id": "jellyfin-now-playing", @@ -1236,12 +1199,11 @@ "plugin_path": "plugins/jellyfin-now-playing", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.3.2", - "ledmatrix_min_version": "3.4.0", - "commit": "d6223d0f6f6299238fa6c952143004a2f45e3c92" + "latest_version": "1.3.3", + "ledmatrix_min_version": "3.4.0" }, { "id": "incoming-packages", @@ -1262,13 +1224,12 @@ "plugin_path": "plugins/incoming-packages", "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "https://raw.githubusercontent.com/ChuckBuilds/ledmatrix-plugins/main/plugins/incoming-packages/assets/screenshot.png", - "latest_version": "1.1.3", + "latest_version": "1.1.4", "icon": "fas fa-box", - "ledmatrix_min_version": "3.4.0", - "commit": "8eb980951dedda974ebca5d9eac76ca4e7aa7601" + "ledmatrix_min_version": "3.4.0" }, { "id": "pomodoro-timer", @@ -1289,13 +1250,12 @@ "plugin_path": "plugins/pomodoro-timer", "stars": 0, "downloads": 0, - "last_updated": "2026-09-29", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", - "latest_version": "1.3.9", + "latest_version": "1.3.10", "icon": "fa-hourglass-half", - "ledmatrix_min_version": "2.0.0", - "commit": "843588025a81197056f8d96779ccb2be19337ab8" + "ledmatrix_min_version": "2.0.0" }, { "id": "sleeper-fantasy", @@ -1343,10 +1303,9 @@ "last_updated": "2026-10-02", "verified": true, "screenshot": "https://raw.githubusercontent.com/ChuckBuilds/ledmatrix-plugins/main/plugins/blackjack/assets/hero.png", - "latest_version": "1.3.1", + "latest_version": "1.3.2", "icon": "fa-diamond", - "ledmatrix_min_version": "3.2.0", - "commit": "f9ced936bab32e05df5dd71bcabbc94972aa634b" + "ledmatrix_min_version": "3.2.0" }, { "id": "ledmatrix-nascar", diff --git a/plugins/7-segment-clock/README.md b/plugins/7-segment-clock/README.md index 913bc151..3dfdf808 100644 --- a/plugins/7-segment-clock/README.md +++ b/plugins/7-segment-clock/README.md @@ -227,12 +227,12 @@ the clock renders at 1.8× actually leaves a 3-pixel gap. panel, plus spacing 2 and 10 on a 64x32 panel](../../docs/assets/7-segment-clock/digit-spacing.png) -**One caveat worth knowing.** The auto-scaler sizes the digits from the width -of the digits alone — it does not account for the spacing you add on top. On a -wide panel that is harmless. On a narrow one it is not: as the bottom row above -shows, `digit_spacing: 10` on a 64×32 panel pushes the outer digits off both -edges. If you are on a 64-wide panel, keep `digit_spacing` at 4 or below, or -turn off the leading zero to buy back a digit's width. +The auto-scaler counts the spacing as part of the width it fits to the panel, +so no value in the 0–10 range pushes digits off the edge. On a narrow panel a +large spacing is paid for in digit size instead: `digit_spacing: 10` on a +64×32 panel shrinks the digits to about 0.6× so the whole string still fits. +If you are on a 64-wide panel and want the digits as large as possible, keep +`digit_spacing` low, or turn off the leading zero to buy back a digit's width. ### `color` @@ -267,7 +267,7 @@ white. The clock has no fixed size. On every frame it computes a scale factor: ```text -scale = min( (panel_width * 0.9) / total_digit_width, +scale = min( (panel_width * 0.9) / (total_digit_width + total_spacing), (panel_height * 0.9) / 32 ) clamped to the range 0.5 – 3.0 @@ -316,9 +316,10 @@ means UTC. That is the Pi's system clock, not the plugin. Check `timedatectl` and that NTP is reaching a time server. -**The outer digits are cut off.** -See the caveat under [`digit_spacing`](#digit_spacing) — reduce the spacing, or -move to a wider panel. +**The digits look small.** +A large [`digit_spacing`](#digit_spacing) or a leading zero takes width the +auto-scaler would otherwise give the digits. Reduce the spacing, or turn off +`has_leading_zero`. **The colon is missing.** If it is missing only some of the time, that is @@ -338,6 +339,8 @@ is missing permanently, check that `assets/images/separator.png` exists. ├── config_schema.json # Settings schema; source of truth for defaults ├── requirements.txt # pytz ├── test_render_polarity.py # Regression test for digit rendering +├── test_frame_cadence.py # Colon blink and per-minute repaint at the core's frame rate +├── test/ # Safety-harness config and golden images ├── README.md ├── LICENSE └── assets/ diff --git a/plugins/7-segment-clock/manifest.json b/plugins/7-segment-clock/manifest.json index 0d24bdb3..614a7fe1 100644 --- a/plugins/7-segment-clock/manifest.json +++ b/plugins/7-segment-clock/manifest.json @@ -1,7 +1,7 @@ { "id": "7-segment-clock", "name": "7-Segment Clock", - "version": "1.0.10", + "version": "1.0.11", "description": "Display a retro-style 7-segment clock with customizable colors", "author": "LEDMatrix", "entry_point": "manager.py", @@ -24,6 +24,12 @@ "default_duration": 15, "config_schema": "config_schema.json", "versions": [ + { + "version": "1.0.11", + "released": "2026-10-02", + "ledmatrix_min_version": "2.0.0", + "notes": "README only: the digit_spacing caveat described a bug fixed earlier. The auto-scaler counts the spacing in the width it fits, so no spacing pushes digits off a 64-wide panel; a large spacing shrinks the digits instead. The scale formula and troubleshooting entry now say so, and the file list includes test_frame_cadence.py and test/." + }, { "version": "1.0.10", "released": "2026-09-28", @@ -88,5 +94,5 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28" + "last_updated": "2026-10-02" } diff --git a/plugins/afl-scoreboard/CHANGELOG.md b/plugins/afl-scoreboard/CHANGELOG.md index 4a31794a..b0ca39f0 100644 --- a/plugins/afl-scoreboard/CHANGELOG.md +++ b/plugins/afl-scoreboard/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [1.35.2] - 2026-10-02 + +### Documentation +- README: `scroll_settings.scroll_speed` is documented with its real default, + `50` px/s (it said `1.0` "pixels per step"). +- README: documents `odds_update_interval` (`3600` s), + `live_odds_update_interval` (`60` s), the full-screen + `scroll_card.switch_show_date` / `switch_show_time` switches, + `customization.odds_text`, and the nested `game_limits` / + `display_options` copies and which one wins. No change in behaviour. + ## [1.35.1] - 2026-10-01 ### Changed diff --git a/plugins/afl-scoreboard/README.md b/plugins/afl-scoreboard/README.md index 53e6076b..f0d1ef54 100644 --- a/plugins/afl-scoreboard/README.md +++ b/plugins/afl-scoreboard/README.md @@ -358,6 +358,8 @@ also driving a panel, and raising the polling rate rarely helps. | `recent_update_interval` | `3600` | Fetch interval for the recent screen | | `upcoming_update_interval` | `3600` | Fetch interval for the upcoming screen | | `stale_game_timeout` | `300` | How long a live game may go without an update before it is dropped from the rotation. Guards against a game the API stops reporting sitting on the board forever | +| `odds_update_interval` | `3600` | How long fetched betting odds for a recent or upcoming game are reused before asking again (60–86400 s). Only matters with `show_odds` on | +| `live_odds_update_interval` | `60` | The same for a live game (30–3600 s) | | `no_data_interval_seconds` | `300` | How long to wait between live checks when nothing is on. Backs off further the longer nothing is found | | `live_idle_max_interval_seconds` | `900` | Ceiling for that back-off. Raise it out of season; lower it to notice the first game of the night sooner | | `schedule_lookback_days` | `14` | How far back the recent screen can see | @@ -383,6 +385,7 @@ also driving a panel, and raising the polling rate rarely helps. | `show_records` | `false` | **Advanced.** Draw each team's season record in the bottom corners | | `show_ranking` | `false` | **Advanced.** Draw a rank badge. AFL publishes no poll, so this shows nothing. **Hidden from the config form since 1.30.1 (still declared) — the empty rank badge also replaced the record.** | | `show_odds` | `true` | Draw the betting line. **ESPN publishes no odds for AFL** — see [Known Limitations](#known-limitations) | +| `game_limits.*`, `display_options.*` | as above | **Advanced.** Nested copies of the game-limit keys (`recent_games_to_show` … `other_games_divisions`) and of `show_records` / `show_ranking` / `show_odds`. Both copies render in the web UI and both are read: a nested value you changed from its default wins, otherwise the root key decides. Set one place or the other, not both | | `customization.favorite_result_colors.enabled` | `false` | Colour a finished game's score by whether your favourite won | | `customization.favorite_result_colors.win_color` | `[0, 255, 0]` | **Advanced.** Colour for a win | | `customization.favorite_result_colors.loss_color` | `[255, 0, 0]` | **Advanced.** Colour for a loss | @@ -414,8 +417,10 @@ scroll mode. | `scroll_card.switch_date_format` | `numeric` | `numeric` (9/4), `abbrev` (Sep 4), `day_first` (4 Sep), `numeric_day_first` (4/9), `weekday` (Fri Sep 4), `inherit` | | `scroll_card.date_format` | `abbrev` | Same set, minus `inherit` | | `scroll_card.time_format` | `12h` | `12h` (7:40PM) or `24h` (19:40) | -| `scroll_card.show_date` | `true` | Draw the date at all | -| `scroll_card.show_time` | `true` | Draw the start time at all | +| `scroll_card.show_date` | `true` | Draw the date on the scroll and Vegas cards | +| `scroll_card.show_time` | `true` | Draw the start time on the scroll and Vegas cards | +| `scroll_card.switch_show_date` | `true` | Draw the date on the full-screen upcoming scoreboard | +| `scroll_card.switch_show_time` | `true` | Draw the start time on the full-screen upcoming scoreboard | | `scroll_card.switch_recent_show_date` | `true` | Draw the game's date along the bottom of the full-screen recent scoreboard, written in `switch_date_format` | | `scroll_card.swap_date_time` | `false` | Put the time above the date instead of below | | `scroll_card.center_gap` | *(auto)* | Fixed pixel gap in the middle of a scroll card | @@ -437,7 +442,7 @@ Scroll-mode-only settings: | Option | Default | What it does | |--------|---------|--------------| -| `scroll_settings.scroll_speed` | `1.0` | **Advanced.** Pixels per step | +| `scroll_settings.scroll_speed` | `50` | **Advanced.** Scroll speed in pixels per second (0.01–200) | | `scroll_settings.scroll_delay` | `0.01` | **Advanced.** Ignored; kept so saved configs still load. Scrolling is paced to the panel refresh; `scroll_speed` sets the speed. **Hidden from the config form since 1.30.0 (still declared).** | | `scroll_settings.gap_between_games` | `24` | **Advanced.** Blank pixels between cards | | `scroll_settings.game_card_width` | `128` | **Advanced.** Width of one card | @@ -484,6 +489,7 @@ Every text element on the card can be restyled independently. All of these are | `customization.status_text` | `4x6-font.ttf` | `6` | `[255, 255, 255]` | | `customization.detail_text` | `4x6-font.ttf` | `6` | `[255, 255, 255]` | | `customization.rank_text` | `PressStart2P-Regular.ttf` | `10` | `[255, 255, 255]` | +| `customization.odds_text` | `4x6-font.ttf` | `6` | `[0, 255, 0]` | Each group takes `font`, `font_size` and `text_color` (an RGB array): diff --git a/plugins/afl-scoreboard/manifest.json b/plugins/afl-scoreboard/manifest.json index 73f23b02..32a8b6b7 100644 --- a/plugins/afl-scoreboard/manifest.json +++ b/plugins/afl-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "afl-scoreboard", "name": "AFL Scoreboard", - "version": "1.35.1", + "version": "1.35.2", "author": "ChuckBuilds", "description": "Live, recent, and upcoming AFL (Australian Football League) games with real-time scores and game status.", "category": "sports", @@ -18,6 +18,12 @@ "afl_upcoming" ], "versions": [ + { + "version": "1.35.2", + "released": "2026-10-02", + "ledmatrix_min_version": "3.8.0", + "notes": "README only: scroll_settings.scroll_speed is documented with its real default (50 px/s, not 1.0); documents odds_update_interval / live_odds_update_interval, the full-screen scroll_card.switch_show_date / switch_show_time switches, customization.odds_text, and the nested game_limits / display_options copies and which one wins. No change in behaviour." + }, { "version": "1.35.1", "released": "2026-10-01", @@ -531,7 +537,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2026-09-30", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/afl-scoreboard/test_afl_plugin.py b/plugins/afl-scoreboard/test_afl_plugin.py index 477bfd32..ae5dc6cd 100644 --- a/plugins/afl-scoreboard/test_afl_plugin.py +++ b/plugins/afl-scoreboard/test_afl_plugin.py @@ -36,6 +36,15 @@ def _install_thirdparty_stubs() -> None: """ def _mod(name, **attrs): m = sys.modules.get(name) + if m is None: + # Prefer the real package when it is installed: a stubbed + # ``requests`` is not a package, so a core that imports + # ``requests.models`` (fetch_service) would fail to load. + try: + import importlib + m = importlib.import_module(name) + except ImportError: + m = None if m is None: m = types.ModuleType(name) sys.modules[name] = m diff --git a/plugins/baseball-scoreboard/CHANGELOG.md b/plugins/baseball-scoreboard/CHANGELOG.md index e5880bab..466e3911 100644 --- a/plugins/baseball-scoreboard/CHANGELOG.md +++ b/plugins/baseball-scoreboard/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [1.57.0] - 2026-10-02 + +### Added +- `customization.layout.time` (`x_offset` / `y_offset`, advanced). The start + time on the upcoming scoreboard and on upcoming cards has always read this + offset, but the schema had no `time` group and sets + `additionalProperties: false`, so it could not be set. + +### Changed +- `customization.layout.record.x_offset` is hidden: nothing reads it. The two + records sit at opposite edges and move with `away_x_offset` / + `home_x_offset`. The README said it shifted both records; it now says it is + ignored. Kept declared so saved configs keep validating. +- README documents `scroll_card.switch_show_date` / `switch_show_time`, + `customization.odds_text` and the full list of layout groups. + ## [1.56.1] - 2026-10-02 ### Changed diff --git a/plugins/baseball-scoreboard/README.md b/plugins/baseball-scoreboard/README.md index 43898881..535c19f5 100644 --- a/plugins/baseball-scoreboard/README.md +++ b/plugins/baseball-scoreboard/README.md @@ -746,7 +746,8 @@ and the full-screen scoreboard. | `date_format` | `abbrev` | Scroll and Vegas: `abbrev` (Sep 19), `numeric` (9/19), `day_first` (19 Sep), `numeric_day_first` (19/9), `weekday` (Fri Sep 19) | | `switch_date_format` | `numeric` | The same set for the full-screen scoreboard, plus `inherit` | | `time_format` | `12h` | `12h` (7:40PM) or `24h` (19:40) | -| `show_date` / `show_time` | `true` | Drop either line **on an upcoming card** | +| `show_date` / `show_time` | `true` | Drop either line **on an upcoming card** (scroll and Vegas only) | +| `switch_show_date` / `switch_show_time` | `true` | The same for the full-screen upcoming scoreboard. Separate switches because the originals predate that display reading this block, and sharing them would have changed what existing boards draw | | `swap_date_time` | `false` | Swap the two lines. Each display starts from its own order, so this flips rather than forces: cards put the time on top, the full-screen stack puts the date on top | The two `*_date_format` keys have different defaults on purpose: the cards have @@ -882,8 +883,10 @@ full-screen scoreboard and on the scroll and Vegas cards alike. | `status_text` | Status lines such as "Next Game" | | `detail_text` | Small detail lines | | `rank_text` | Team rankings (unused here — no baseball league publishes a poll) | +| `odds_text` | The spread and over/under (4x6 at 6 by default, like `detail_text`) | -Colours are `[r, g, b]` or `"#RRGGBB"`, and every default is white: +Colours are `[r, g, b]` or `"#RRGGBB"`, and every default is white except +`odds_text`, which defaults to the green the odds have always used (`[0, 255, 0]`): ```json { @@ -894,15 +897,18 @@ Colours are `[r, g, b]` or `"#RRGGBB"`, and every default is white: ``` `customization.layout` nudges individual elements by `x_offset` / `y_offset` -for panels where something sits slightly wrong. The record group takes two extra +for panels where something sits slightly wrong. Its groups are `home_logo`, +`away_logo`, `score`, `date`, `time`, `status`, `record` and `odds`, each +defaulting to `0`/`0` (`ranking` is hidden and ignored: the rank badge sits in +the record slot). The record group takes two extra keys, because the two records sit at opposite edges: | Option | Default | What it does | |--------|---------|--------------| -| `customization.layout.record.x_offset` | `0` | Shifts both records. | +| `customization.layout.record.x_offset` | `0` | Ignored and hidden in the settings form; nothing reads it. Use the two keys below. | | `customization.layout.record.y_offset` | `0` | Vertical nudge for both. | -| `customization.layout.record.away_x_offset` | `0` | Extra horizontal shift for the away record alone. | -| `customization.layout.record.home_x_offset` | `0` | Extra horizontal shift for the home record alone. | +| `customization.layout.record.away_x_offset` | `0` | Horizontal shift for the away record, drawn from the left edge. | +| `customization.layout.record.home_x_offset` | `0` | Horizontal shift for the home record, drawn from the right edge. | The centre-gap settings size the strip kept clear down the middle of a scroll or Vegas card, so the score is not drawn over the team logos. They do not affect the diff --git a/plugins/baseball-scoreboard/config_schema.json b/plugins/baseball-scoreboard/config_schema.json index 137fd1a1..17a01d85 100644 --- a/plugins/baseball-scoreboard/config_schema.json +++ b/plugins/baseball-scoreboard/config_schema.json @@ -2534,6 +2534,26 @@ }, "additionalProperties": false }, + "time": { + "type": "object", + "title": "Game Time", + "description": "Moves the start time on the upcoming scoreboard and on an upcoming card.", + "properties": { + "x_offset": { + "x-advanced": true, + "type": "integer", + "default": 0, + "description": "Horizontal offset from center (default: 0)" + }, + "y_offset": { + "x-advanced": true, + "type": "integer", + "default": 0, + "description": "Vertical offset from default position (default: 0)" + } + }, + "additionalProperties": false + }, "status": { "type": "object", "title": "Game Status/Inning", @@ -2560,9 +2580,10 @@ "properties": { "x_offset": { "x-advanced": true, + "x-display": "hidden", "type": "integer", "default": 0, - "description": "Horizontal offset from default position (default: 0)" + "description": "Horizontal offset from default position (default: 0) HIDDEN: nothing reads it. The two records sit at opposite edges and move with away_x_offset / home_x_offset. Kept declared so saved configs keep validating." }, "y_offset": { "x-advanced": true, @@ -2574,13 +2595,13 @@ "x-advanced": true, "type": "integer", "default": 0, - "description": "Additional horizontal offset for away team record (default: 0)" + "description": "Horizontal offset for the away record, drawn from the left edge (default: 0)" }, "home_x_offset": { "x-advanced": true, "type": "integer", "default": 0, - "description": "Additional horizontal offset for home team record (default: 0)" + "description": "Horizontal offset for the home record, drawn from the right edge (default: 0)" } }, "additionalProperties": false, @@ -2632,6 +2653,7 @@ "away_logo", "score", "date", + "time", "status", "record", "ranking", diff --git a/plugins/baseball-scoreboard/manifest.json b/plugins/baseball-scoreboard/manifest.json index 842e896b..eed7544f 100644 --- a/plugins/baseball-scoreboard/manifest.json +++ b/plugins/baseball-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "baseball-scoreboard", "name": "Baseball Scoreboard", - "version": "1.56.1", + "version": "1.57.0", "update_interval": 60, "author": "ChuckBuilds", "description": "Live, recent, and upcoming baseball games across MLB, MiLB, and NCAA Baseball with real-time scores and schedules", @@ -31,6 +31,12 @@ "branch": "main", "plugin_path": "plugins/baseball-scoreboard", "versions": [ + { + "version": "1.57.0", + "released": "2026-10-02", + "ledmatrix_min_version": "3.8.0", + "notes": "Adds customization.layout.time (advanced): the upcoming start time has always read this offset, but the schema had no time group and forbids extra keys, so it could not be set. Hides customization.layout.record.x_offset, which nothing reads (the records move with away_x_offset / home_x_offset; the README said it shifted both). README documents scroll_card.switch_show_date / switch_show_time, customization.odds_text and the layout groups." + }, { "version": "1.56.1", "released": "2026-10-02", @@ -836,7 +842,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-30", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/basketball-scoreboard/CHANGELOG.md b/plugins/basketball-scoreboard/CHANGELOG.md index b848530a..7e49d6be 100644 --- a/plugins/basketball-scoreboard/CHANGELOG.md +++ b/plugins/basketball-scoreboard/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [1.42.0] - 2026-10-02 + +### Added +- `customization.layout.record.away_x_offset` / `home_x_offset`, + `customization.layout.date` and `customization.layout.time` (advanced). + The scoreboard and cards have always read these offsets, but the schema did + not declare them and sets `additionalProperties: false`, so none could be + set: the records could only be moved vertically, and the recent date and the + upcoming card's date and time not at all. + +### Changed +- `customization.layout.record.x_offset` is hidden: nothing reads it (the + records move with the two keys above). Kept declared so saved configs keep + validating. +- README: the layout table named elements the schema does not have + (`status_text`, `records`); it now lists the real ones. Documents + `scroll_card.switch_show_date` / `switch_show_time` and the per-league + `odds_update_interval` / `live_odds_update_interval`. + ## [1.41.2] - 2026-10-02 ### Changed diff --git a/plugins/basketball-scoreboard/README.md b/plugins/basketball-scoreboard/README.md index 0a4061d9..9269ab37 100644 --- a/plugins/basketball-scoreboard/README.md +++ b/plugins/basketball-scoreboard/README.md @@ -357,6 +357,8 @@ All **Advanced**. | `.live_update_interval` | 5–300 s | `30` | How often live game data refreshes. | | `.recent_update_interval` | 60–86400 s | `3600` | How often the finished-games list is rebuilt. This also sets how soon a game that has just ended can appear — lower it if you want results sooner. | | `.upcoming_update_interval` | 60–86400 s | `3600` | How often the upcoming-games list is rebuilt. Selection and the non-favorite rotation both run on the display side, so this governs only the fetch. | +| `.odds_update_interval` | 60–86400 s | `3600` | **Advanced.** How often betting odds are refreshed for recent and upcoming games. Only matters when Show Odds is on. | +| `.live_odds_update_interval` | 30–3600 s | `60` | **Advanced.** How often odds are refreshed for games in progress. Only matters when Show Odds is on. | | `.stale_game_timeout` | 60–3600 s | `300` | Drop a live game the API has stopped updating. | ### Display options @@ -423,7 +425,8 @@ Plugin-wide, not per league. | Date Format | `scroll_card.date_format` | `abbrev` | Scroll and Vegas cards: `Sep 19`, `9/19`, `19 Sep`, `19/9`, or `Fri Sep 19`. | | Full-Screen Date Format | `scroll_card.switch_date_format` | `numeric` | **Advanced.** The same for the full-screen scoreboard, plus `inherit`. It has its own default because the two displays disagree about what is normal. | | Time Format | `scroll_card.time_format` | `12h` | 12- or 24-hour clock. | -| Show Date / Show Time | `scroll_card.show_date`, `scroll_card.show_time` | `true` | Drop either line. | +| Show Date / Show Time | `scroll_card.show_date`, `scroll_card.show_time` | `true` | Drop either line from the scroll and Vegas cards. | +| Full-Screen Show Date / Show Time | `scroll_card.switch_show_date`, `scroll_card.switch_show_time` | `true` | The same for the full-screen scoreboard. Separate switches because the originals predate this display reading the block, and sharing them would have changed what existing boards draw. | | Full-Screen Recent Date | `scroll_card.switch_recent_show_date` | `true` | Draw the date a finished game was played along the bottom of the full-screen recent scoreboard, written in the Full-Screen Date Format. | | Swap Date and Time | `scroll_card.swap_date_time` | `false` | Flip the two lines. Each display starts from its own order, so this flips rather than forces. | @@ -475,10 +478,10 @@ Nudge any element in pixels. All default to `0`, all **Advanced**, all under |---|---|---| | `home_logo`, `away_logo` | `x_offset`, `y_offset` | Default logo position | | `score` | `x_offset`, `y_offset` | Panel centre | -| `status_text` | `x_offset`, `y_offset` | Centre horizontally, top vertically | -| `date` | `x_offset`, `y_offset` | Centre horizontally, default position vertically | -| `time` | `x_offset`, `y_offset` | Centre horizontally, the date's position vertically | -| `records` | `away_x_offset`, `home_x_offset`, `y_offset` | Away from the left, home from the right, both from the bottom | +| `status` | `x_offset`, `y_offset` | Centre horizontally, top vertically. Also moves the date and time on an upcoming scoreboard, which are drawn as status text | +| `date` | `x_offset`, `y_offset` | The date along the bottom of a recent scoreboard, and the date on an upcoming scroll or Vegas card | +| `time` | `x_offset`, `y_offset` | The start time on an upcoming scroll or Vegas card | +| `record` | `away_x_offset`, `home_x_offset`, `y_offset` | Away from the left, home from the right, both from the bottom. The rank badge sits in the same slot. `record.x_offset` is hidden and ignored | | `odds` | `x_offset`, `y_offset` | Default odds position | ## Favorite team result colours diff --git a/plugins/basketball-scoreboard/config_schema.json b/plugins/basketball-scoreboard/config_schema.json index 5e26550e..2faceb36 100644 --- a/plugins/basketball-scoreboard/config_schema.json +++ b/plugins/basketball-scoreboard/config_schema.json @@ -2940,15 +2940,16 @@ }, "additionalProperties": false }, - "record": { + "date": { "type": "object", - "title": "Team Records", + "title": "Game Date", + "description": "Moves the date along the bottom of the full-screen recent scoreboard, and the date on an upcoming scroll or Vegas card. (The full-screen upcoming date and time move with Game Status.)", "properties": { "x_offset": { "x-advanced": true, "type": "integer", "default": 0, - "description": "Horizontal offset from default position (default: 0)" + "description": "Horizontal offset from center (default: 0)" }, "y_offset": { "x-advanced": true, @@ -2959,6 +2960,59 @@ }, "additionalProperties": false }, + "time": { + "type": "object", + "title": "Game Time", + "description": "Moves the start time on an upcoming scroll or Vegas card.", + "properties": { + "x_offset": { + "x-advanced": true, + "type": "integer", + "default": 0, + "description": "Horizontal offset from center (default: 0)" + }, + "y_offset": { + "x-advanced": true, + "type": "integer", + "default": 0, + "description": "Vertical offset from default position (default: 0)" + } + }, + "additionalProperties": false + }, + "record": { + "type": "object", + "title": "Team Records", + "properties": { + "x_offset": { + "x-advanced": true, + "type": "integer", + "default": 0, + "description": "Horizontal offset from default position (default: 0) HIDDEN: nothing reads it. The two records sit at opposite edges and move with Away / Home Horizontal Offset. Kept declared so saved configs keep validating.", + "x-display": "hidden" + }, + "y_offset": { + "x-advanced": true, + "type": "integer", + "default": 0, + "description": "Vertical offset for both records (default: 0)" + }, + "away_x_offset": { + "x-advanced": true, + "type": "integer", + "default": 0, + "description": "Horizontal offset for the away record, drawn from the left edge (default: 0)" + }, + "home_x_offset": { + "x-advanced": true, + "type": "integer", + "default": 0, + "description": "Horizontal offset for the home record, drawn from the right edge (default: 0)" + } + }, + "additionalProperties": false, + "description": "Moves the win-loss record (or rank badge) in the bottom corners. The rank badge is drawn in the record slot, so these are its offsets too." + }, "ranking": { "type": "object", "title": "Team Rankings", @@ -3005,6 +3059,8 @@ "away_logo", "score", "status", + "date", + "time", "record", "ranking", "odds" diff --git a/plugins/basketball-scoreboard/manifest.json b/plugins/basketball-scoreboard/manifest.json index 57182547..1961b8bc 100644 --- a/plugins/basketball-scoreboard/manifest.json +++ b/plugins/basketball-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "basketball-scoreboard", "name": "Basketball Scoreboard", - "version": "1.41.2", + "version": "1.42.0", "update_interval": 60, "description": "Live, recent, and upcoming basketball games across NBA, NCAA Men's, NCAA Women's, and WNBA with real-time scores, schedules, and March Madness tournament support", "author": "ChuckBuilds", @@ -19,6 +19,12 @@ "branch": "main", "plugin_path": "plugins/basketball-scoreboard", "versions": [ + { + "version": "1.42.0", + "released": "2026-10-02", + "ledmatrix_min_version": "3.8.0", + "notes": "Adds customization.layout.record.away_x_offset / home_x_offset and the layout.date and layout.time groups (advanced). The scoreboard and cards have always read these offsets, but the schema did not declare them and forbids extra keys, so the records could only be moved vertically and the recent date and upcoming card date/time not at all. Hides layout.record.x_offset, which nothing reads. README: the layout table named elements the schema does not have; it now lists the real ones, and documents scroll_card.switch_show_date / switch_show_time and the per-league odds_update_interval / live_odds_update_interval." + }, { "version": "1.41.2", "released": "2026-10-02", diff --git a/plugins/birdnet-go/README.md b/plugins/birdnet-go/README.md index adb09327..3d9f8df3 100644 --- a/plugins/birdnet-go/README.md +++ b/plugins/birdnet-go/README.md @@ -117,7 +117,7 @@ Every `birdnet_api.poll_interval` seconds (default 60) the plugin calls: | `/api/v2/analytics/species/daily` | today's per-species counts | | `/api/v2/media/species-image?name=` | the species photo | -Photos are cached in memory and on disk for 30 days, keyed by scientific name; failed lookups are remembered for the session so we don't hammer the API. If the image endpoint is unreachable the layout falls back to text-only. +Photos are cached in memory and on disk for 30 days, keyed by scientific name; a failed lookup is not retried for 30 minutes, so the API isn't hammered but a photo that timed out on its first request still turns up. If the image endpoint is unreachable the layout falls back to text-only. Polling keeps running even when MQTT is enabled, so a broker outage can't freeze the display. diff --git a/plugins/birdnet-go/manager.py b/plugins/birdnet-go/manager.py index 95f38c40..504a3f1e 100644 --- a/plugins/birdnet-go/manager.py +++ b/plugins/birdnet-go/manager.py @@ -53,6 +53,9 @@ _MAX_SOURCE_IMAGES = 32 _MAX_PANEL_IMAGES = 16 +# Seconds before a species whose image fetch failed is tried again. +_IMAGE_RETRY_S = 1800 + # Narrow fallback face for long names beside the photo; crisp only at 7px. NARROW_FONT_PATH = 'assets/fonts/4x6-font.ttf' NARROW_FONT_SIZE = 7 @@ -156,7 +159,10 @@ def __init__(self, plugin_id: str, config: Dict[str, Any], # species -> decoded source PIL, and (species, w, h) -> panel-sized frame. self._species_img_cache: "OrderedDict[str, Image.Image]" = OrderedDict() self._panel_img_cache: "OrderedDict[Tuple[str, int, int], Image.Image]" = OrderedDict() - self._species_img_failed: set = set() # species we already failed to fetch + # species -> when its image fetch last failed; retried after + # _IMAGE_RETRY_S. A permanent set meant one timeout (BirdNET-Go + # downloads a photo upstream on first request) hid it until restart. + self._species_img_failed: Dict[str, float] = {} self._pending_image_fetch: Optional[str] = None self._scroll_pos = 0.0 self._scroll_cache: Optional[Image.Image] = None @@ -703,7 +709,7 @@ def _matrix_dims(self) -> Tuple[int, int]: def _fetch_species_image(self, species: str) -> Optional[Image.Image]: if not self.api_base_url or not species: return None - if species in self._species_img_failed: + if self._image_failed_recently(species): return None # Disk cache via cache_manager (base64 of PNG bytes) @@ -723,7 +729,7 @@ def _fetch_species_image(self, species: str) -> Optional[Image.Image]: resp = requests.get(url, timeout=self.api_timeout) if resp.status_code != 200: self.logger.warning("Species image HTTP %s for %s", resp.status_code, species) - self._species_img_failed.add(species) + self._species_img_failed[species] = time.time() return None img = Image.open(BytesIO(resp.content)) img.load() @@ -738,9 +744,13 @@ def _fetch_species_image(self, species: str) -> Optional[Image.Image]: return img except Exception as e: self.logger.warning("Error fetching species image for %s: %s", species, e) - self._species_img_failed.add(species) + self._species_img_failed[species] = time.time() return None + def _image_failed_recently(self, species: str) -> bool: + failed_at = self._species_img_failed.get(species) + return failed_at is not None and time.time() - failed_at < _IMAGE_RETRY_S + def _resize_image(self, img: Image.Image, box_w: int, box_h: int) -> Image.Image: """Fill the box edge to edge, zoomed in so the bird isn't a speck. @@ -1111,7 +1121,7 @@ def update(self) -> None: deadline = time.time() + max(2.0, self.api_timeout * 1.5) for species in wanted: if (not species or species in self._species_img_cache - or species in self._species_img_failed): + or self._image_failed_recently(species)): continue if time.time() >= deadline: self.logger.debug("Image warm-up budget spent; resuming next update") diff --git a/plugins/birdnet-go/manifest.json b/plugins/birdnet-go/manifest.json index b6eb146b..f4ddcc38 100644 --- a/plugins/birdnet-go/manifest.json +++ b/plugins/birdnet-go/manifest.json @@ -1,7 +1,7 @@ { "id": "birdnet-go", "name": "BirdNET-Go", - "version": "1.2.7", + "version": "1.2.8", "author": "ChuckBuilds", "description": "Show what BirdNET-Go is hearing. One screen cycles a different species each turn \u2014 name, confidence, how many times it has been heard today, and a photo \u2014 and a second shows today's stats: species count, total detections and the most-heard species. Polls the BirdNET-Go REST API, with optional MQTT for instant pop-ups.", "category": "integration", @@ -21,6 +21,12 @@ "entry_point": "manager.py", "class_name": "BirdNetGoPlugin", "versions": [ + { + "released": "2026-10-02", + "version": "1.2.8", + "changelog": "A species photo that failed to load is retried after 30 minutes instead of never. BirdNET-Go downloads a photo upstream on its first request, so one slow first fetch used to leave that species text-only until a restart.", + "ledmatrix_min_version": "2.0.0" + }, { "released": "2026-09-28", "version": "1.2.7", @@ -84,7 +90,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/blackjack/config_schema.json b/plugins/blackjack/config_schema.json index 257b3f6e..cd2fa4a3 100644 --- a/plugins/blackjack/config_schema.json +++ b/plugins/blackjack/config_schema.json @@ -106,7 +106,7 @@ "minimum": 0, "maximum": 255 }, - "x-widget": "color" + "x-widget": "color-picker" }, "player_color": { "type": "array", @@ -124,7 +124,7 @@ "minimum": 0, "maximum": 255 }, - "x-widget": "color" + "x-widget": "color-picker" }, "felt_color": { "type": "array", @@ -142,7 +142,7 @@ "minimum": 0, "maximum": 255 }, - "x-widget": "color" + "x-widget": "color-picker" }, "card_interval": { "type": "number", diff --git a/plugins/blackjack/manifest.json b/plugins/blackjack/manifest.json index b92543a9..b8dfdf02 100644 --- a/plugins/blackjack/manifest.json +++ b/plugins/blackjack/manifest.json @@ -1,7 +1,7 @@ { "id": "blackjack", "name": "Blackjack", - "version": "1.3.1", + "version": "1.3.2", "author": "ChuckBuilds", "description": "Deals one hand of Las Vegas blackjack per rotation. Cards slide in and flip face up a few seconds apart, the player follows basic strategy, the dealer plays the house rules, and the hand ends on a BLACKJACK! / BUST! / PUSH banner. Every hand comes off a real shuffled shoe, so no two rotations are the same.", "entry_point": "manager.py", @@ -34,6 +34,12 @@ "update_interval": 3600, "default_duration": 22, "versions": [ + { + "version": "1.3.2", + "released": "2026-10-02", + "ledmatrix_min_version": "3.2.0", + "notes": "The three colour settings (dealer, player, felt) use the web UI's color-picker widget. They declared x-widget color, which the web UI does not recognise, so they showed as plain comma-separated text boxes. Values and defaults are unchanged." + }, { "version": "1.3.1", "released": "2026-10-02", diff --git a/plugins/calendar/README.md b/plugins/calendar/README.md index 925141ca..f75c9103 100644 --- a/plugins/calendar/README.md +++ b/plugins/calendar/README.md @@ -49,10 +49,10 @@ size and scaled up so the pixels stay pixels.* 1. In Cloud Console, go to **APIs & Services → Credentials** 2. Click **Create Credentials → OAuth client ID** -3. Choose **Desktop application** — the plugin uses - `InstalledAppFlow.run_local_server()` - (`plugins/calendar/manager.py:346-348`), which requires this client - type. "TV and Limited Input Device" will not work. +3. Choose **Desktop application**. The sign-in flow in + `calendar_registration.py` redirects to a loopback address + (`http://127.0.0.1`), which Google only allows for this client type. + "TV and Limited Input Device" will not work. 4. Download the JSON file 5. Save it as `credentials.json` in the calendar plugin directory (typically `plugin-repos/calendar/credentials.json`) @@ -64,15 +64,20 @@ size and scaled up so the pixels stay pixels.* 2. Open the **Calendar** tab in the second nav row (added once the plugin is installed) 3. Click the **Authenticate Google Calendar** button -4. Follow the OAuth flow in your browser -5. The token is saved automatically into the plugin directory +4. Open the link it gives you and approve access. Google then redirects your + browser to a `http://127.0.0.1/...` page that fails to load -- that is + expected. Copy that page's full address and paste it back into the web UI +5. The token is saved automatically into the plugin directory as `token.pickle` **Option B: Use Registration Script** ```bash -cd plugins/calendar +cd plugin-repos/calendar python calendar_registration.py ``` +Run from a terminal, the script opens a local browser for the consent screen, +so it needs a machine with a desktop browser. On a headless Pi use Option A. + **The plugin never signs in by itself.** If it has no usable token (none yet, or a refresh fails because access was revoked or the token expired), it logs the problem and the panel shows **Auth needed / See web UI** instead of events. @@ -110,7 +115,7 @@ widget and first-time authentication) and rarely need editing by hand. | `enabled` | `false` | Enable or disable the plugin | | `credentials_file` | `"credentials.json"` | Google OAuth credentials file (uploaded via the config UI) | | `google_auth` | `""` | Not a setting you type into. It is the **Step 2: Connect Your Google Account** button in the web UI (`x-widget: google-oauth`), which runs the consent flow described above. Google redirects to a page that fails to load — that is expected; copy the address back into the field to finish. | -| `token_file` | `"token.pickle"` | Where the OAuth token is stored after first-time auth (auto-created) | +| `token_file` | `"token.pickle"` | Token file the plugin reads, relative to the plugin directory. Not in the web form. The sign-in flow always writes `token.pickle`, so leave this at the default | | `calendars` | `["primary"]` | Calendar IDs to display — use the calendar picker after authenticating, or `"primary"` for your default | | `max_events` | `3` | Maximum upcoming events to show (1–10) | | `show_all_day_events` | `true` | Include all-day events | @@ -168,8 +173,9 @@ Events from all calendars are merged and sorted by start time. ### Update Frequency - Set `update_interval` to balance freshness vs. API usage -- 300 seconds (5 minutes) is recommended -- Lower values may hit API rate limits +- The default is 3600 seconds (hourly); 300 (5 minutes) suits a calendar that + changes during the day +- Each refresh makes one request per configured calendar ### Timezone @@ -212,9 +218,11 @@ See [List of timezones](https://en.wikipedia.org/wiki/List_of_tz_database_time_z - Review logs for errors **Long titles cut off:** -- Text automatically wraps to 2 lines -- Titles longer than 2 lines will be truncated -- Consider shortening event names in Google Calendar +- Titles wrap onto as many lines as fit below the date (two on a 32-pixel-tall + panel with the default fonts, four at 64 pixels) +- When the title needs more lines than fit, the last visible line ends in `...` +- Use a smaller `customization.title_text` font, or shorten event names in + Google Calendar ## Calendar IDs @@ -238,8 +246,8 @@ Google Calendar API has rate limits: - **Free tier**: 1,000,000 queries per day - **Per user**: 10 requests per second -With default settings (300s interval): -- 288 requests per day (well within limits) +With the default 3600s interval: 24 requests per calendar per day. At 300s: +288 per calendar per day. Both are well within the limits. ## Security Notes diff --git a/plugins/calendar/manager.py b/plugins/calendar/manager.py index 74a698ea..97a70a9d 100644 --- a/plugins/calendar/manager.py +++ b/plugins/calendar/manager.py @@ -189,7 +189,9 @@ def _load_fonts(self): title_settings = customization.get('title_text', {}) # Get font names and sizes from config (with defaults) - datetime_font_name = datetime_settings.get('font', '4x6-font.ttf') + # Mirrors config_schema.json, whose default is PressStart2P for + # both elements; a normal load merges that default in anyway. + datetime_font_name = datetime_settings.get('font', 'PressStart2P-Regular.ttf') datetime_font_size = datetime_settings.get('font_size', 8) title_font_name = title_settings.get('font', 'PressStart2P-Regular.ttf') title_font_size = title_settings.get('font_size', 8) @@ -452,8 +454,10 @@ def update(self) -> None: ) continue - # Sort all events by start time - all_events.sort(key=lambda x: x['start'].get('dateTime', x['start'].get('date', ''))) + # Sort all events by start time. Compare instants, not strings: + # each calendar's dateTime carries its own UTC offset, so text order + # put e.g. 10:00-07:00 ahead of 12:00-04:00 (an hour earlier). + all_events.sort(key=self._event_start_ts) # Limit to max_events self.events = all_events[:self.max_events] @@ -730,7 +734,26 @@ def _format_event_time(self, event: Dict) -> str: self.logger.warning(f"Error formatting event time: {e}") return 'All Day' - + + def _event_start_ts(self, event: Dict) -> float: + """Start of an event as a UNIX timestamp, for merging calendars. + + An all-day event starts at local midnight of its date, so it still + sorts ahead of that day's timed events. Unparseable starts sort last. + """ + start = event.get('start', {}) + try: + if 'dateTime' in start: + return datetime.fromisoformat(start['dateTime'].replace('Z', '+00:00')).timestamp() + if 'date' in start: + day = datetime.fromisoformat(start['date']) + if self.timezone is not None and hasattr(self.timezone, 'localize'): + return self.timezone.localize(day).timestamp() + return day.replace(tzinfo=timezone.utc).timestamp() + except (TypeError, ValueError, AttributeError) as e: + self.logger.warning(f"Error reading event start for sorting: {e}") + return float('inf') + def _display_no_events(self): """Display message when no events are available.""" width, height = self._fresh_frame() diff --git a/plugins/calendar/manifest.json b/plugins/calendar/manifest.json index 2fbe6087..052d82b1 100644 --- a/plugins/calendar/manifest.json +++ b/plugins/calendar/manifest.json @@ -1,7 +1,7 @@ { "id": "calendar", "name": "Google Calendar", - "version": "1.2.10", + "version": "1.2.11", "author": "ChuckBuilds", "description": "Display upcoming events from Google Calendar with date, time, and event details. Shows next 1-3 events with automatic rotation and timezone support.", "category": "productivity", @@ -35,6 +35,12 @@ } ], "versions": [ + { + "version": "1.2.11", + "released": "2026-10-02", + "ledmatrix_min_version": "3.3.0", + "notes": "Events merged from several calendars are ordered by when they actually start. They were sorted by the start text, and each calendar writes its times with its own UTC offset, so a 10:00-07:00 event sorted ahead of a 12:00-04:00 one that starts an hour earlier -- and with max_events applied after the sort, the earlier event could be dropped. The code default for the date/time font now matches the schema (PressStart2P); a normal load merges the schema default, so rendering is unchanged. README: the setup step no longer cites the removed run_local_server() call, the web sign-in step describes pasting back the 127.0.0.1 address, the registration script is noted as needing a desktop browser, token_file is noted as best left at its default (sign-in always writes token.pickle), and the update-interval and long-title notes match the code (default 3600s; titles wrap to as many lines as fit). Core floor unchanged." + }, { "version": "1.2.10", "released": "2026-10-02", diff --git a/plugins/christmas-countdown/README.md b/plugins/christmas-countdown/README.md index 5b707568..5bdfbc33 100644 --- a/plugins/christmas-countdown/README.md +++ b/plugins/christmas-countdown/README.md @@ -36,15 +36,17 @@ The text has three states, driven by the date: | When | Shows | |------|-------| -| Before 25 December | `N DAYS UNTIL CHRISTMAS` | +| Before 25 December | `N DAYS UNTIL CHRISTMAS` (`1 DAY UNTIL CHRISTMAS` on the 24th) | | On 25 December | `MERRY CHRISTMAS` | -| After 25 December | `MERRY CHRISTMAS`, until the count to next year begins | +| From 26 December | The count to next year's Christmas, e.g. `364 DAYS UNTIL CHRISTMAS` | ![The countdown 114 days out, a week out, the day before, and on Christmas Day itself](../../docs/assets/christmas-countdown/countdown.png) -On a panel **narrower than 64 pixels** the last word is abbreviated to `XMAS` -so the text still fits. The count is computed from today's date in the +When `CHRISTMAS` does not fit the right half of the panel in the smallest font +(any panel narrower than about 93 pixels, so every 64-pixel-wide panel) the +last word is abbreviated to `XMAS` so the text still fits; 96 pixels and wider +spell it out. The count is computed from today's date in the LEDMatrix `timezone` setting (the host's system time if none is set) every time the screen is drawn, so it changes at midnight in that timezone. @@ -120,24 +122,26 @@ panels](../../docs/assets/christmas-countdown/panel-sizes.png) ## The Tree Image -The tree is `assets/christmas_tree.png`, a small pixel-art PNG that ships with -the plugin and is scaled to the space available. +The tree is `tree icon.png` in the plugin's own directory, a 256×256 image that +ships with the plugin and is scaled down to the space available. If that file +is missing the plugin falls back to `assets/christmas_tree.png`, a small 32×32 +pixel-art tree that also ships with it. -If that file is missing, the plugin draws a simple tree programmatically +If both files are missing, the plugin draws a simple tree programmatically instead — and **that** is the only situation in which `tree_color` applies. With -the bundled image present, as it is on any normal install, `tree_color` has no -visible effect. The schema says so; it is repeated here because "green tree +the bundled images present, as they are on any normal install, `tree_color` has +no visible effect. The schema says so; it is repeated here because "green tree colour" reads like a setting that should work. -To regenerate the bundled image: +To regenerate the fallback image: ```bash python3 generate_tree_image.py ``` That script writes `assets/christmas_tree.png` and nothing else — it is an -asset generator, not a preview of the plugin, so it cannot drift from what the -plugin draws. +asset generator, not a preview of the plugin, and it does not touch +`tree icon.png`, which is the image normally drawn. --- @@ -153,16 +157,18 @@ system timezone, which is used when none is set) if it disagrees with your calendar. **It says MERRY CHRISTMAS in July.** -It should not — that message is shown on and shortly after 25 December only. If -you see it out of season, check the system date. +It should not — that message is shown on 25 December only; from the 26th the +count to next year starts. If you see it out of season, check the system date +and the LEDMatrix `timezone` setting. **The text says XMAS instead of CHRISTMAS.** -That is deliberate on panels narrower than 64 pixels, where the full word does -not fit. +That is deliberate on panels narrower than about 93 pixels (every 64-pixel-wide +panel), where the full word does not fit the right half. **I changed the tree colour and nothing happened.** -`tree_color` only applies when `assets/christmas_tree.png` is missing. With the -bundled image in place the tree comes from the PNG. +`tree_color` only applies when both `tree icon.png` and +`assets/christmas_tree.png` are missing. With the bundled images in place the +tree comes from the PNG. **I changed the tree size and nothing happened.** `tree_size` is not applied — see @@ -180,9 +186,11 @@ christmas-countdown/ ├── manager.py # ChristmasCountdownPlugin ├── config_schema.json # Settings schema; source of truth for defaults ├── generate_tree_image.py # Regenerates assets/christmas_tree.png +├── tree icon.png # The tree normally drawn ├── assets/ -│ └── christmas_tree.png -├── test/ +│ └── christmas_tree.png # Fallback tree when tree icon.png is missing +├── test/ # Harness fixture and golden images +├── test_christmas_day_count.py └── README.md ``` @@ -202,6 +210,13 @@ harness can check it renders correctly at every panel size: python scripts/check_plugin.py --plugin christmas-countdown --plugin-dir /path/to/ledmatrix-plugins/plugins --out-dir /tmp/preview ``` +The day-count regression tests (count before the first `update()`, midnight +rollover, the LEDMatrix timezone) run against a core checkout: + +```bash +LEDMATRIX_CORE=/path/to/LEDMatrix python plugins/christmas-countdown/test_christmas_day_count.py +``` + To watch it live in the emulator instead: ```bash diff --git a/plugins/christmas-countdown/manager.py b/plugins/christmas-countdown/manager.py index 69111319..32f91d0d 100644 --- a/plugins/christmas-countdown/manager.py +++ b/plugins/christmas-countdown/manager.py @@ -2,13 +2,13 @@ Christmas Countdown Plugin for LEDMatrix Displays a countdown to Christmas with a stylized Christmas tree logo -and festive text. Shows "MERRY CHRISTMAS" on and after Christmas Day. +and festive text. Shows "MERRY CHRISTMAS" on Christmas Day. Features: - Stylized Christmas tree logo (image or programmatic fallback) - Adaptive text: "N DAYS UNTIL CHRISTMAS" or "N DAYS UNTIL XMAS" on smaller displays - Traditional holiday colors (green tree, red text) -- Automatic "MERRY CHRISTMAS" message on/after Dec 25 +- Automatic "MERRY CHRISTMAS" message on Dec 25 API Version: 1.0.0 """ @@ -487,10 +487,11 @@ def display(self, force_clear: bool = False) -> None: if self.is_christmas or self.days_until_christmas == 0: message = "MERRY CHRISTMAS" else: + day_word = "DAY" if self.days_until_christmas == 1 else "DAYS" if use_xmas: - message = f"{self.days_until_christmas} DAYS UNTIL XMAS" + message = f"{self.days_until_christmas} {day_word} UNTIL XMAS" else: - message = f"{self.days_until_christmas} DAYS UNTIL CHRISTMAS" + message = f"{self.days_until_christmas} {day_word} UNTIL CHRISTMAS" # Check if we need to redraw (prevent blinking) # Only redraw if the message changed or force_clear is True @@ -539,7 +540,7 @@ def display(self, force_clear: bool = False) -> None: if len(parts) >= 4: lines = [ f"{self.days_until_christmas}", - "DAYS", + day_word, "UNTIL", "XMAS" ] @@ -551,7 +552,7 @@ def display(self, force_clear: bool = False) -> None: if len(parts) >= 4: lines = [ f"{self.days_until_christmas}", - "DAYS", + day_word, "UNTIL", "CHRISTMAS" ] diff --git a/plugins/christmas-countdown/manifest.json b/plugins/christmas-countdown/manifest.json index a22bee43..03809fca 100644 --- a/plugins/christmas-countdown/manifest.json +++ b/plugins/christmas-countdown/manifest.json @@ -1,7 +1,7 @@ { "id": "christmas-countdown", "name": "Christmas Countdown", - "version": "2.0.3", + "version": "2.0.4", "author": "ChuckBuilds", "description": "Display a countdown to Christmas with a stylized Christmas tree logo and festive text", "category": "holiday", @@ -17,6 +17,12 @@ "christmas-countdown" ], "versions": [ + { + "version": "2.0.4", + "released": "2026-10-02", + "ledmatrix_min_version": "2.0.0", + "notes": "On 24 December the panel reads 1 DAY UNTIL CHRISTMAS (or XMAS) instead of 1 DAYS. Documentation: the README said MERRY CHRISTMAS stays up after the 25th; from the 26th the plugin counts to next year's Christmas. It put the XMAS abbreviation below 64px wide, but every 64px-wide panel abbreviates and 96px and wider spell it out (the cut is whether CHRISTMAS fits the right half, about 93px). And it named assets/christmas_tree.png as the tree, but the plugin draws tree icon.png and only falls back to that file, so tree_color applies only when both are missing. The module docstring had the same after-Christmas claim." + }, { "version": "2.0.3", "released": "2026-09-28", @@ -70,7 +76,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/clock-simple/README.md b/plugins/clock-simple/README.md index be545424..e485c524 100644 --- a/plugins/clock-simple/README.md +++ b/plugins/clock-simple/README.md @@ -1,8 +1,8 @@ # Simple Clock -A clean time-and-date clock for your LED matrix. It picks the largest text that -fits your panel, shrinks and abbreviates when it has to, and lets you restyle -the time, the date and the AM/PM marker independently. +A clean time-and-date clock for your LED matrix. It centres itself on any +panel, abbreviates the weekday and date when they would not fit, and lets you +restyle the time, the date and the AM/PM marker independently. ![The clock on a 128x32 panel showing 3:07 PM in white with PM in pale yellow, and Wednesday over September 2nd in @@ -136,10 +136,11 @@ correct for the whole turn however long you make it. ### `update_interval` -Seconds between refreshes, default `1`. With `show_seconds` on you want `1`; -with it off you could raise it, but there is little to gain — the plugin only -pushes pixels to the panel when the rendered image actually changes, so an -unchanged minute costs nothing either way. +How often the LEDMatrix core calls the plugin's background `update()`, +default `1`. It has no visible effect on this plugin: the clock re-reads the +time on every frame it draws, so the display (seconds included) stays current +whatever this is set to. The plugin also only pushes pixels to the panel when +the rendered image actually changes, so an unchanged minute costs nothing. ### `timezone` @@ -148,7 +149,7 @@ An IANA timezone name such as `America/Chicago`, `Europe/London` or 1. `timezone` in this plugin's config 2. The global LEDMatrix `timezone` setting -3. The host system's timezone +3. UTC, when neither is set Leave it unset on a normal install. Set it only when you want this clock to show a *different* zone from the rest of your board — a second clock for a @@ -165,8 +166,9 @@ element simply is not used. ### `show_seconds` -Appends `:SS`, giving `3:07:09` or `15:07:09`. The string is wider, so on a -narrow panel the clock picks a smaller size to fit it. +Appends `:SS`, giving `3:07:09` or `15:07:09`. The string is wider, and the +time is not resized to fit: with a large `font_size` on a narrow panel it can +run past the edges, so pick a smaller font there. ![Four panels comparing 12-hour against 24-hour, and seconds off against on](../../docs/assets/clock-simple/time-format.png) @@ -181,8 +183,8 @@ when the hour rolls from `9:59` to `10:00`. ### `show_date` -Set to `false` for a time-only clock. The time is then drawn larger, since it -has the whole panel to itself. +Set to `false` for a time-only clock. The time stays where it is, near the top +in the same font; the space below is left empty. ### `date_format` @@ -245,8 +247,8 @@ look sharp while a `.ttf` scaled to an odd size looks soft. ## Panel Sizes and How Text Shrinks -The clock measures its text against the panel and steps down rather than -overflowing. +The clock measures the weekday and date against the panel and shortens them +rather than overflowing. The time itself is never resized. ![The same clock on 64x32, 128x32, 128x64 and 256x32 panels](../../docs/assets/clock-simple/panel-sizes.png) @@ -256,8 +258,8 @@ Two behaviours worth knowing, both visible in the 64×32 panel above: - **The weekday abbreviates.** `Wednesday` becomes `Wed` when the full name will not fit. - **The date falls back through shorter forms.** `September 2nd` becomes - `Sep 2nd`, then `Sep 2`; the numeric formats drop to a two-digit year - (`09/02/26`) and then to `09-02`. + `Sep 2nd`, then `Sep 2`; `MM/DD/YYYY` and `DD/MM/YYYY` drop to a two-digit + year (`09/02/26`), and `YYYY-MM-DD` drops to `09-02`. The clock only shortens what it must, so a wider panel keeps the full text. On a 128×64 panel the time and date sit in separate bands with the space between @@ -316,8 +318,10 @@ missing from `assets/fonts/`. ```text clock-simple/ ├── manifest.json # Plugin metadata and version history -├── manager.py # ClockSimplePlugin +├── manager.py # SimpleClock ├── config_schema.json # Settings schema; source of truth for defaults +├── test_position_and_timezone.py # Offset and timezone-fallback tests +├── test/ # Render-harness config and golden PNGs ├── README.md └── LICENSE ``` diff --git a/plugins/clock-simple/manifest.json b/plugins/clock-simple/manifest.json index c74e7d6e..529e3894 100644 --- a/plugins/clock-simple/manifest.json +++ b/plugins/clock-simple/manifest.json @@ -1,7 +1,7 @@ { "id": "clock-simple", "name": "Simple Clock", - "version": "1.1.3", + "version": "1.1.4", "author": "ChuckBuilds", "description": "A simple clock display with current time and date", "category": "time", @@ -16,6 +16,12 @@ "clock-simple" ], "versions": [ + { + "version": "1.1.4", + "released": "2026-10-02", + "ledmatrix_min_version": "2.0.0", + "notes": "README only. It said the clock picks a smaller size to fit and draws the time larger when show_date is off; neither happens -- only the weekday and date are shortened to fit, and the time keeps its font and position. Also corrects update_interval (it has no visible effect, since the time is re-read every frame), the timezone fallback when nothing is set (UTC, not the host zone), which numeric date format falls back to MM-DD, and the class name in the file listing. No change in behaviour." + }, { "version": "1.1.3", "released": "2026-09-29", @@ -73,7 +79,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/countdown/README.md b/plugins/countdown/README.md index 0b8381f7..3e70e031 100644 --- a/plugins/countdown/README.md +++ b/plugins/countdown/README.md @@ -132,12 +132,12 @@ count, and there is no separate "today" state: ![Six panels showing each rung of that ladder: 113 Days, Tomorrow, 10h 30m, 30m, NOW! and 3d ago](../../docs/assets/countdown/count-formats.png) -Two consequences worth knowing: +Three consequences worth knowing: -- **The hours-and-minutes rungs only appear if you set `target_time`.** Without - it the target is midnight, so an event "today" is already in the past by the - time anyone is looking at the panel. Set `target_time` for anything where the - hour matters. +- **Without `target_time` the target is midnight at the start of + `target_date`.** The hours-and-minutes rungs then count down through the day + *before*, and on the day itself the countdown has already passed. Set + `target_time` for anything where the hour matters. - **A passed countdown is hidden by default.** `Nd ago` is only ever visible with [`show_expired`](#global-settings) turned on. - **The last 24 hours are drawn in yellow.** Once the target is less than a day @@ -277,7 +277,8 @@ makes the name inherit `font_family`. ### Images `image_path` may be absolute, relative to the working directory, or relative to -the plugins repository root — the plugin tries each in that order. +the LEDMatrix directory the plugin is installed under — the plugin tries each in +that order. - Square images work best; the image area is a third of the panel width by the full height. @@ -321,7 +322,7 @@ writes `Tomorrow` rather than `1 Day`. See **The image is not showing.** Check the log for `Image not found` — the path is tried absolute, then relative -to the working directory, then relative to the repository root. Also check +to the working directory, then relative to the LEDMatrix directory. Also check `layout_preset` is not `text-only`, which ignores images by design. **I picked a font and nothing changed.** diff --git a/plugins/countdown/manager.py b/plugins/countdown/manager.py index c167f5e3..22e711f5 100644 --- a/plugins/countdown/manager.py +++ b/plugins/countdown/manager.py @@ -735,8 +735,12 @@ def display(self, force_clear: bool = False) -> None: # An explicit image_width/image_height from the advanced modal # still wins over both. _default_img_w = dw if layout_preset == 'image-only' else (dw // 3) - img_w = layout.get('image_width') or _default_img_w - img_h = layout.get('image_height') or dh + # Capped at the panel first: image-right places the box at + # dw - img_w, so a width wider than the panel put it at a negative + # x -- the image landed on the left, cropped, and the text area + # had a negative width, which pushed the text off the panel. + img_w = min(layout.get('image_width') or _default_img_w, dw) + img_h = min(layout.get('image_height') or dh, dh) if _has_px_override: # User set explicit pixel positions — honour them directly diff --git a/plugins/countdown/manifest.json b/plugins/countdown/manifest.json index 4feb0f31..0de772f7 100644 --- a/plugins/countdown/manifest.json +++ b/plugins/countdown/manifest.json @@ -1,7 +1,7 @@ { "id": "countdown", "name": "Countdown Display", - "version": "3.3.4", + "version": "3.3.5", "author": "ChuckBuilds", "description": "Create and manage customizable countdowns with images. Perfect for birthdays, holidays, events, and special occasions.", "entry_point": "manager.py", @@ -21,6 +21,12 @@ ">=2.0.0" ], "versions": [ + { + "version": "3.3.5", + "released": "2026-10-02", + "ledmatrix_min_version": "2.0.0", + "notes": "An image-right image with an Image Width wider than the panel stays on the panel. The box was placed at panel width minus image width, a negative x, so the image landed on the left edge, cropped, and the text area had a negative width that pushed the countdown text off the panel. The width and height are now capped at the panel before the preset places the box. README: without target_time the hours-and-minutes rungs count down through the day before the target date (the old text said they never appear), and a relative image_path is resolved against the LEDMatrix directory, not the plugins repository." + }, { "version": "3.3.4", "released": "2026-10-02", @@ -123,5 +129,5 @@ "license": "GPL-3.0", "homepage": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/countdown", "config_schema": "config_schema.json", - "last_updated": "2026-09-28" + "last_updated": "2026-10-02" } diff --git a/plugins/countdown/test_layout_overrides.py b/plugins/countdown/test_layout_overrides.py index eeaef0a5..d2af9b59 100644 --- a/plugins/countdown/test_layout_overrides.py +++ b/plugins/countdown/test_layout_overrides.py @@ -160,6 +160,14 @@ def red(p): and rows and rows[-1] - rows[0] + 1 == 28, "%s rows %s" % (span, (rows[0], rows[-1]) if rows else None)) +print("an image-right width wider than the panel stays on the panel") +frame = render(layout_preset="image-right", image_path=str(RED), + layout={"image_width": 200}) +span = extent(frame, range(H), red) +check("the box is capped at the panel width, so the image is centred, not cropped", + span is not None and span[0] > 0 and span[1] < W + and abs((span[0] + span[1]) / 2 - W / 2) <= 1, str(span)) + print() print("%d failed" % len(failures)) sys.exit(1 if failures else 0) diff --git a/plugins/cricket-scoreboard/README.md b/plugins/cricket-scoreboard/README.md index 36e905b5..90364fe3 100644 --- a/plugins/cricket-scoreboard/README.md +++ b/plugins/cricket-scoreboard/README.md @@ -72,14 +72,14 @@ Configured under the `cricket-scoreboard` key in `config/config.json`. See | `show_favorite_teams_only` | `false` | Restrict *international* matches to those featuring a favorite team | | `live_game_duration` / `recent_game_duration` / `upcoming_game_duration` | 20 / 15 / 15 | Per-match on-screen seconds | | `non_favorite_live_game_duration` | `0` | Shorter turn for live matches without a favorite (0 = same as `live_game_duration`) | -| `update_interval_seconds` / `live_update_interval` | 3600 / 30 | Data refresh cadence | +| `update_interval_seconds` / `live_update_interval` | 3600 / 30 | Data refresh cadence (the live cadence also applies once an upcoming match's start time has passed, so a match that has just started is picked up within seconds) | | `series_discovery_interval` | `86400` | How often numeric series ids are re-resolved | | `recent_games_to_show` / `upcoming_games_to_show` | 5 / 5 | Match counts per mode | | `live_priority` | `true` | Live matches interrupt the normal rotation | | `display_modes` | all on | Toggle live / recent / upcoming | | `dynamic_duration`, `mode_durations` | off / null | Auto-size or cap each mode's total time | | `background_service.request_timeout` | 30 | HTTP request timeout (seconds) | -| `customization` | — | Fonts + colors for score / overs / team / status / detail text | +| `customization` | — | Fonts + colors for score / team / status / detail text | ### Every setting @@ -94,7 +94,7 @@ complete list, at the exact paths the schema expects — the schema sets | `favorite_competitions` | `["international", "ipl", "bbl"]` | Domestic competitions (keys from competitions.json) to follow. Include 'international' to follow Test/ODI/T20I tours for your favorite_teams. | | `exclude_teams` | *(empty)* | Teams to always hide from live rotation and recent/final scores (spoiler protection). Takes precedence over favorite_teams. | | `show_favorite_teams_only` | `false` | Only show matches involving a favorite national team. Domestic-league matches are always governed by favorite_competitions. | -| `display_duration` | `15` | Duration in seconds to display each match (5–60). | +| `display_duration` | `15` | Seconds a mode stays on screen when it has no matches to time by (5–60). Per-match time comes from the three durations below. | | `live_game_duration` | `20` | Duration in seconds to display each live match before rotating to the next (10–120). | | `non_favorite_live_game_duration` | `0` | Duration in seconds for live matches that do NOT involve a favorite team. 0 (default) = use live_game_duration for every live match (0–120). | | `recent_game_duration` | `15` | Duration in seconds to display each recent match (5–60). | @@ -117,15 +117,15 @@ complete list, at the exact paths the schema expects — the schema sets | `display_modes.upcoming` | `true` | Show upcoming matches. | | `background_service.request_timeout` | `30` | Timeout in seconds for each request to ESPN (5–120). | | `customization.score_text.font` | `"PressStart2P-Regular.ttf"` | one of `PressStart2P-Regular.ttf`, `4x6-font.ttf`, `5by7.regular.ttf`. | -| `customization.score_text.font_size` | `10` | (4–16). | -| `customization.period_text.font` | `"PressStart2P-Regular.ttf"` | one of `PressStart2P-Regular.ttf`, `4x6-font.ttf`, `5by7.regular.ttf`. | -| `customization.period_text.font_size` | `8` | (4–16). | +| `customization.score_text.font_size` | `8` | (4–16). | +| `customization.period_text.font` | `"PressStart2P-Regular.ttf"` | Ignored and hidden in the settings form: nothing draws with it (the overs line uses `detail_text`, the session/day `status_text`). Kept so saved configs validate. | +| `customization.period_text.font_size` | `8` | Ignored, as above. | | `customization.team_name.font` | `"PressStart2P-Regular.ttf"` | one of `PressStart2P-Regular.ttf`, `4x6-font.ttf`, `5by7.regular.ttf`. | | `customization.team_name.font_size` | `8` | (4–16). | | `customization.status_text.font` | `"4x6-font.ttf"` | one of `PressStart2P-Regular.ttf`, `4x6-font.ttf`, `5by7.regular.ttf`. | -| `customization.status_text.font_size` | `6` | (4–16). | +| `customization.status_text.font_size` | `7` | (4–16). | | `customization.detail_text.font` | `"4x6-font.ttf"` | one of `PressStart2P-Regular.ttf`, `4x6-font.ttf`, `5by7.regular.ttf`. | -| `customization.detail_text.font_size` | `6` | (4–16). | +| `customization.detail_text.font_size` | `7` | (4–16). | | `customization.colors.score_color` | `"#FFFFFF"` | Color for the runs/wickets score. | | `customization.colors.batting_color` | `"#00FF66"` | Highlight color for the team currently batting. | | `customization.colors.detail_color` | `"#FFD200"` | Color for run rate / target detail text. | @@ -211,8 +211,8 @@ live but not *whose*. Two things to keep in mind: - The weight is per **plugin**, not per game. With four games live this - scoreboard still occupies one slot at a time and picks between its own games - using `favorite_live_boost`; these weights control how often the scoreboard + scoreboard still occupies one slot at a time and rotates through its own + games, favorites first; these weights control how often the scoreboard itself comes round. - More slots make the cycle **longer**, not faster — everything else appears proportionally less often. And appearing more often only helps if the data is diff --git a/plugins/cricket-scoreboard/config_schema.json b/plugins/cricket-scoreboard/config_schema.json index e5723dfc..a2014a27 100644 --- a/plugins/cricket-scoreboard/config_schema.json +++ b/plugins/cricket-scoreboard/config_schema.json @@ -363,7 +363,8 @@ "period_text": { "type": "object", "title": "Overs / Session", - "description": "Font settings for overs, session and day text", + "x-display": "hidden", + "description": "Font settings for overs, session and day text HIDDEN: nothing draws with it. The overs line uses Details and the session/day uses Status Messages. Kept declared so saved configs keep validating.", "properties": { "font": { "type": "string", diff --git a/plugins/cricket-scoreboard/cricket_data_fetcher.py b/plugins/cricket-scoreboard/cricket_data_fetcher.py index 924c8f7c..ebd3f382 100644 --- a/plugins/cricket-scoreboard/cricket_data_fetcher.py +++ b/plugins/cricket-scoreboard/cricket_data_fetcher.py @@ -373,6 +373,10 @@ def _parse_event(self, ev: Dict[str, Any], series_id: str, series_name: str, "status_summary": status.get("summary", "") or "", "status_detail": stype.get("detail", "") or "", "status_short": stype.get("shortDetail", "") or "", + # A Test's break ("Stumps", "Lunch") and day ("Day 2"): + # shortDetail is only "Live" while one is in progress. + "status_description": stype.get("description", "") or "", + "status_session": status.get("session", "") or "", "period": int(status.get("period", 0) or 0), "date": comp.get("date") or ev.get("date") or "", "start_time_utc": self._parse_iso(comp.get("date") or ev.get("date")), @@ -419,7 +423,9 @@ def _parse_competitor(self, c: Dict[str, Any]) -> Dict[str, Any]: "short_name": team.get("shortDisplayName") or team.get("name") or "", "abbr": team.get("abbreviation") or "", "logo_url": logo_url, - "winner": bool(c.get("winner", False)), + # ESPN's cricket feed sends this as the string "true"/"false"; + # bool("false") is True, which drew the losing side green too. + "winner": str(c.get("winner", "")).strip().lower() == "true", "score_str": c.get("score", "") or "", "innings": innings, "records": records, diff --git a/plugins/cricket-scoreboard/cricket_renderer.py b/plugins/cricket-scoreboard/cricket_renderer.py index ec5f13f1..19731c8f 100644 --- a/plugins/cricket-scoreboard/cricket_renderer.py +++ b/plugins/cricket-scoreboard/cricket_renderer.py @@ -105,16 +105,17 @@ def _load_one(self, element_cfg: Dict[str, Any], default_font: str, return ImageFont.load_default() def _load_fonts(self, custom: Dict[str, Any]) -> None: + # Defaults mirror config_schema.json (score 8, status/detail 7). self._fonts["score"] = self._load_one(custom.get("score_text"), - "PressStart2P-Regular.ttf", 10) + "PressStart2P-Regular.ttf", 8) self._fonts["period"] = self._load_one(custom.get("period_text"), "PressStart2P-Regular.ttf", 8) self._fonts["team"] = self._load_one(custom.get("team_name"), "PressStart2P-Regular.ttf", 8) self._fonts["status"] = self._load_one(custom.get("status_text"), - "4x6-font.ttf", 6) + "4x6-font.ttf", 7) self._fonts["detail"] = self._load_one(custom.get("detail_text"), - "4x6-font.ttf", 6) + "4x6-font.ttf", 7) # ---- draw helpers ----------------------------------------------------- # @@ -189,15 +190,40 @@ def _draw_team_crest(self, img: Image.Image, draw: ImageDraw.ImageDraw, # ---- innings helpers -------------------------------------------------- # + @staticmethod + def _batted_innings(team: Dict[str, Any]) -> List[Dict[str, Any]]: + """The innings this side actually batted, in order. + + ESPN gives each side a linescore for every period of the match, + including the ones it spent bowling: those carry 0 runs, 0 wickets and + the opponent's overs, with isBatting false. isBatting marks the periods + a side batted in (not just the one in progress), so it stays true on a + finished first innings. Counting the bowling rows drew a Test score as + "0 & 278 & 0/0" and a side yet to bat as "0/0". + """ + return [inn for inn in (team.get("innings") or []) + if inn.get("is_batting") or inn.get("runs") or inn.get("wickets")] + @staticmethod def _batting_innings(team: Dict[str, Any]) -> Optional[Dict[str, Any]]: - innings = team.get("innings") or [] - if not innings: - return None - for inn in innings: - if inn.get("is_batting"): - return inn - return innings[-1] + """This side's latest batted innings, or None when it has not batted.""" + batted = CricketRenderer._batted_innings(team) + return batted[-1] if batted else None + + @staticmethod + def _at_crease(inn: Optional[Dict[str, Any]], + other: Optional[Dict[str, Any]]) -> bool: + """Whether `inn` is the innings in progress, given the other side's. + + Both sides keep isBatting true on every innings they batted, so the + flag alone highlighted the side that batted first for the whole chase. + The later innings (higher period) is the one in progress. + """ + if not inn: + return False + if other and inn.get("period", 0) != other.get("period", 0): + return inn.get("period", 0) > other.get("period", 0) + return bool(inn.get("is_batting")) @staticmethod def _score_text(team: Dict[str, Any]) -> str: @@ -236,8 +262,8 @@ def render_live_limited_overs(self, match: Dict[str, Any]) -> Image.Image: # Scores center. away_inn = self._batting_innings(away) home_inn = self._batting_innings(home) - away_batting = bool(away_inn and away_inn.get("is_batting")) - home_batting = bool(home_inn and home_inn.get("is_batting")) + away_batting = self._at_crease(away_inn, home_inn) + home_batting = self._at_crease(home_inn, away_inn) cx = self.width // 2 away_score = self._score_text(away) @@ -248,19 +274,43 @@ def render_live_limited_overs(self, match: Dict[str, Any]) -> Image.Image: self.batting_color if home_batting else self.score_color) # Detail line: overs + run rate for the batting side; RRR/target on chase. - detail = self._live_detail(match, away, home, away_inn, home_inn, - away_batting, home_batting) + parts = self._live_detail_parts(match, away, home, away_inn, home_inn, + away_batting, home_batting) + detail = self._fit_parts(draw, parts, self._fonts["detail"]) if detail: self._draw_centered(draw, detail, cx, self.height - 7, self._fonts["detail"], self.detail_color) return img - def _live_detail(self, match, away, home, away_inn, home_inn, - away_batting, home_batting) -> str: + def _fit_parts(self, draw: ImageDraw.ImageDraw, parts: List[str], + font: ImageFont.ImageFont) -> str: + """The detail parts joined, dropping the least useful until they fit. + + A chase reads "13/20 ov RR 9.4 need 34 RRR 4.9", about 130px in the + detail font -- wider than a 128px panel, so both ends were cut off. + Run rate goes first, then the overs; the chase equation is kept. + """ + if not parts: + return "" + candidates = [parts] + no_rr = [p for p in parts if not p.startswith("RR ")] + if no_rr != parts: + candidates.append(no_rr) + if len(no_rr) > 1: + candidates.append(no_rr[1:]) # the overs are always first + for cand in candidates: + text = " ".join(cand) + if self._text_size(draw, text, font)[0] <= self.width: + return text + return " ".join(candidates[-1]) + + def _live_detail_parts(self, match, away, home, away_inn, home_inn, + away_batting, home_batting) -> List[str]: bat_team = away if away_batting else (home if home_batting else None) bat_inn = away_inn if away_batting else (home_inn if home_batting else None) if not bat_inn: - return match.get("status_short", "") or "" + status = match.get("status_short", "") or "" + return [status] if status else [] max_overs = CricketDataFetcher.parse_max_overs( bat_team.get("score_str", ""), match.get("format", "")) @@ -283,7 +333,7 @@ def _live_detail(self, match, away, home, away_inn, home_inn, rrr = required_run_rate(runs_needed, balls_remaining) if runs_needed > 0 and rrr is not None: parts.append(f"need {runs_needed} RRR {rrr:.1f}") - return " ".join(parts) + return parts def render_live_test(self, match: Dict[str, Any]) -> Image.Image: """Live Test card: no clock -- day + session, both innings scores.""" @@ -310,7 +360,9 @@ def render_live_test(self, match: Dict[str, Any]) -> Image.Image: self._draw_centered(draw, (home.get("abbr") or "")[:4] + " " + home_score, cx, 8 + 8, self._fonts["detail"], self.score_color) - lead = match.get("status_short") or match.get("status_summary") or "" + # The summary ("India lead by 84 runs") first: ESPN's short detail + # is just "Live" while a Test is in progress. + lead = match.get("status_summary") or match.get("status_short") or "" if lead: self._draw_centered(draw, lead[:26], cx, self.height - 7, self._fonts["detail"], self.detail_color) @@ -318,7 +370,7 @@ def render_live_test(self, match: Dict[str, Any]) -> Image.Image: @staticmethod def _test_score_text(team: Dict[str, Any]) -> str: - innings = team.get("innings") or [] + innings = CricketRenderer._batted_innings(team) if not innings: return "-" pieces = [] @@ -332,16 +384,21 @@ def _test_score_text(team: Dict[str, Any]) -> str: return " & ".join(pieces) def _test_session(self, match: Dict[str, Any]) -> str: - for key in ("status_short", "status_detail", "status_summary"): + # ESPN puts the break ("Stumps", "Lunch") in status.type.description + # and the day ("Day 2") in status.session. status.period counts + # innings, not days, so it is not used here: it drew "Day 3" on the + # second day's third innings. + day = match.get("status_session") or "" + for key in ("status_description", "status_short", "status_detail", + "status_summary"): txt = match.get(key) or "" for token in ("Stumps", "Lunch", "Tea", "Close", "Drinks", "Innings Break", "Day"): if token.lower() in txt.lower(): + if day and "day" not in txt.lower(): + txt = f"{txt} {day}" return txt[:22] - period = match.get("period", 0) - if period: - return f"Day {period}" - return "Test" + return day[:22] or "Test" def render_recent(self, match: Dict[str, Any]) -> Image.Image: """Completed match: final scores + result summary.""" @@ -359,8 +416,11 @@ def render_recent(self, match: Dict[str, Any]) -> Image.Image: self._fonts["status"], self.status_color) cx = self.width // 2 - away_line = f"{(away.get('abbr') or '')[:4]} {self._final_score(away)}" - home_line = f"{(home.get('abbr') or '')[:4]} {self._final_score(home)}" + # A Test result needs both innings; the last one alone read as the + # side's whole match. + score = self._test_score_text if match.get("is_test") else self._final_score + away_line = f"{(away.get('abbr') or '')[:4]} {score(away)}" + home_line = f"{(home.get('abbr') or '')[:4]} {score(home)}" away_col = GREEN if away.get("winner") else self.score_color home_col = GREEN if home.get("winner") else self.score_color self._draw_centered(draw, away_line, cx, 7, self._fonts["detail"], away_col) diff --git a/plugins/cricket-scoreboard/manager.py b/plugins/cricket-scoreboard/manager.py index f387e986..f995e9c1 100644 --- a/plugins/cricket-scoreboard/manager.py +++ b/plugins/cricket-scoreboard/manager.py @@ -186,8 +186,11 @@ def update(self) -> None: if not self.is_enabled: return now = time.time() - # Faster refresh when live content is present. - has_live = bool(self.live_matches) + # Faster refresh when live content is present -- or due: a match whose + # start time has passed but was still "pre" at the last fetch. Without + # this the hourly interval left a match that had just started + # unnoticed for up to an hour, so live_priority never fired for it. + has_live = bool(self.live_matches) or self._upcoming_has_started(now) interval = self.live_update_interval if has_live else self.update_interval if now - self._last_update < interval: return @@ -235,6 +238,24 @@ def update(self) -> None: self.logger.info("Cricket update: %d live, %d recent, %d upcoming", len(live), len(recent), len(upcoming)) + def _upcoming_has_started(self, now: float) -> bool: + """Whether an upcoming match's start time passed in the last 12h. + + Bounded so a fixture ESPN never moves off "pre" (abandoned without a + result) cannot hold the fast live cadence forever. + """ + with self._lock: + upcoming = list(self.upcoming_matches) + for m in upcoming: + start = m.get("start_time_utc") + try: + ts = start.timestamp() + except Exception: + continue + if 0 <= now - ts <= 12 * 3600: + return True + return False + def _ensure_logos(self, match: Dict[str, Any]) -> None: if download_missing_logo is None: return @@ -576,9 +597,16 @@ def on_config_change(self, new_config: Dict[str, Any]) -> None: # inside update()/display(), and replacing it would break mutual # exclusion. lock = self._lock + old_fetcher = getattr(self, "fetcher", None) self.__init__(self.plugin_id, new_config, self.display_manager, self.cache_manager, self.plugin_manager) self._lock = lock + # The rebuilt fetcher opened its own HTTP session; close the old one + # rather than leak a connection pool per settings save. + try: + old_fetcher.session.close() + except Exception: + pass # Force a re-discovery + refetch on next update. self._last_update = 0.0 diff --git a/plugins/cricket-scoreboard/manifest.json b/plugins/cricket-scoreboard/manifest.json index c6b381f8..c68f1693 100644 --- a/plugins/cricket-scoreboard/manifest.json +++ b/plugins/cricket-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "cricket-scoreboard", "name": "Cricket Scoreboard", - "version": "1.3.2", + "version": "1.3.3", "author": "ChuckBuilds", "description": "Live, recent, and upcoming cricket matches across international tours (Test/ODI/T20I) and major domestic T20 leagues including the IPL, Big Bash League, The Hundred, PSL, CPL, SA20 and more. Shows runs/wickets/overs, run rates, targets, and match results.", "category": "sports", @@ -22,6 +22,12 @@ "cricket_upcoming" ], "versions": [ + { + "version": "1.3.3", + "released": "2026-10-02", + "ledmatrix_min_version": "2.0.0", + "notes": "Scores now read ESPN's cricket data correctly. ESPN gives each side a linescore for every innings of the match, including the ones it spent bowling (0 runs, 0 wickets), and marks every innings a side batted, not only the one in progress. So a live Test drew \"0 & 278 & 0/0\" for a side that had made 278, a side yet to bat showed \"0/0\", and in a chase both sides were highlighted as batting; only innings a side batted are shown now, and only the innings in progress is highlighted. ESPN sends the winner flag as the string \"false\", which read as true, so a result drew both sides in the winner's green. A live Test shows ESPN's break and day (\"Stumps Day 2\") rather than \"Day 3\" (status.period counts innings, not days), and the bottom line shows the match situation (\"trail by 17 runs\") rather than \"Live\"; a finished Test shows both innings. The chase line drops the run rate, then the overs, when it would run off the panel. A match whose start time has passed is polled at live_update_interval, so live_priority picks it up within seconds instead of up to an hour later. Closes the old HTTP session on a settings save. customization.period_text is hidden (nothing draws with it); font size fallbacks match the schema (8/7/7). The harness fixture now seeds trimmed ESPN data, so CI renders without the network." + }, { "version": "1.3.2", "released": "2026-09-28", @@ -101,7 +107,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/cricket-scoreboard/test/fixtures/mock.json b/plugins/cricket-scoreboard/test/fixtures/mock.json new file mode 100644 index 00000000..9939d253 --- /dev/null +++ b/plugins/cricket-scoreboard/test/fixtures/mock.json @@ -0,0 +1,398 @@ +{ + "cricket:scoreboard:22547": { + "events": [ + { + "id": "1552778", + "name": "Bangladesh v Sri Lanka", + "shortName": "BAN v SL", + "date": "2026-10-03T00:00Z", + "status": { + "period": 2, + "session": "", + "summary": "Bangladesh require 106 runs", + "type": { + "description": "Live", + "detail": "Live", + "shortDetail": "Live", + "state": "in" + } + }, + "competitions": [ + { + "date": "2026-10-03T00:00Z", + "class": { + "eventType": "T20" + }, + "venue": { + "fullName": "Korogi Sports Park, Nisshin" + }, + "competitors": [ + { + "order": 2, + "homeAway": "home", + "winner": "false", + "score": "60/6 (9/20 ov, target 166)", + "team": { + "id": "25", + "name": "Bangladesh", + "displayName": "Bangladesh", + "shortDisplayName": "BAN", + "abbreviation": "BAN" + }, + "linescores": [ + { + "period": 1, + "runs": 0, + "wickets": 0, + "overs": 20.0, + "isBatting": false, + "description": "complete" + }, + { + "period": 2, + "runs": 60, + "wickets": 6, + "overs": 9.0, + "isBatting": true, + "description": "" + } + ] + }, + { + "order": 1, + "homeAway": "away", + "winner": "false", + "score": "165/9", + "team": { + "id": "8", + "name": "Sri Lanka", + "displayName": "Sri Lanka", + "shortDisplayName": "SL", + "abbreviation": "SL" + }, + "linescores": [ + { + "period": 1, + "runs": 165, + "wickets": 9, + "overs": 20.0, + "isBatting": true, + "description": "complete" + }, + { + "period": 2, + "runs": 0, + "wickets": 0, + "overs": 9.0, + "isBatting": false, + "description": "" + } + ] + } + ] + } + ] + }, + { + "id": "1552779", + "name": "India v Pakistan", + "shortName": "IND v PAK", + "date": "2026-10-03T04:30Z", + "status": { + "period": 0, + "session": null, + "summary": "Starts at 13:30 local time", + "type": { + "description": "Scheduled", + "detail": "Scheduled", + "shortDetail": "Scheduled", + "state": "pre" + } + }, + "competitions": [ + { + "date": "2026-10-03T04:30Z", + "class": { + "eventType": "T20" + }, + "venue": { + "fullName": "Korogi Sports Park, Nisshin" + }, + "competitors": [ + { + "order": 1, + "homeAway": "home", + "winner": "false", + "score": "", + "team": { + "id": "6", + "name": "India", + "displayName": "India", + "shortDisplayName": "IND", + "abbreviation": "IND" + }, + "linescores": [] + }, + { + "order": 2, + "homeAway": "away", + "winner": "false", + "score": "", + "team": { + "id": "7", + "name": "Pakistan", + "displayName": "Pakistan", + "shortDisplayName": "PAK", + "abbreviation": "PAK" + }, + "linescores": [] + } + ] + } + ] + } + ] + }, + "cricket:scoreboard:24139": { + "events": [ + { + "id": "1556694", + "name": "Vancouver Anchors v Toronto Sixers", + "shortName": "VAA v TSS", + "date": "2026-10-02T23:00Z", + "status": { + "period": 2, + "session": null, + "summary": "Anchors won by 7 wkts (2b rem)", + "type": { + "description": "Result", + "detail": "Final", + "shortDetail": "Final", + "state": "post" + } + }, + "competitions": [ + { + "date": "2026-10-02T23:00Z", + "class": { + "eventType": "ODI" + }, + "venue": { + "fullName": "BC Place, Vancouver" + }, + "competitors": [ + { + "order": 2, + "homeAway": "home", + "winner": "true", + "score": "112/3 (9.4/10 ov, target 112)", + "team": { + "id": "1506166", + "name": "Vancouver Anchors", + "displayName": "Vancouver Anchors", + "shortDisplayName": "VAA", + "abbreviation": "VAA" + }, + "linescores": [ + { + "period": 1, + "runs": 0, + "wickets": 0, + "overs": 10.0, + "isBatting": false, + "description": "" + }, + { + "period": 2, + "runs": 112, + "wickets": 3, + "overs": 9.4, + "isBatting": true, + "description": "" + } + ] + }, + { + "order": 1, + "homeAway": "away", + "winner": "false", + "score": "111/7", + "team": { + "id": "1506169", + "name": "Toronto Sixers", + "displayName": "Toronto Sixers", + "shortDisplayName": "TSS", + "abbreviation": "TSS" + }, + "linescores": [ + { + "period": 1, + "runs": 111, + "wickets": 7, + "overs": 10.0, + "isBatting": true, + "description": "" + }, + { + "period": 2, + "runs": 0, + "wickets": 0, + "overs": 9.4, + "isBatting": false, + "description": "" + } + ] + } + ] + } + ] + }, + { + "id": "1556695", + "name": "Montreal Royal Tigers v White Rock Warriors", + "shortName": "MRT v WRW", + "date": "2026-10-03T01:45Z", + "status": { + "period": 1, + "session": "", + "summary": "Tigers won toss & fielded", + "type": { + "description": "Live", + "detail": "Live", + "shortDetail": "Live", + "state": "in" + } + }, + "competitions": [ + { + "date": "2026-10-03T01:45Z", + "class": { + "eventType": "ODI" + }, + "venue": { + "fullName": "BC Place, Vancouver" + }, + "competitors": [ + { + "order": 2, + "homeAway": "home", + "winner": "false", + "score": "", + "team": { + "id": "1506164", + "name": "Montreal Royal Tigers", + "displayName": "Montreal Royal Tigers", + "shortDisplayName": "MRT", + "abbreviation": "MRT" + }, + "linescores": [ + { + "period": 1, + "runs": 0, + "wickets": 0, + "overs": 9.1, + "isBatting": false, + "description": "" + } + ] + }, + { + "order": 1, + "homeAway": "away", + "winner": "false", + "score": "133/3 (9.1/10 ov)", + "team": { + "id": "1506167", + "name": "White Rock Warriors", + "displayName": "White Rock Warriors", + "shortDisplayName": "WRW", + "abbreviation": "WRW" + }, + "linescores": [ + { + "period": 1, + "runs": 133, + "wickets": 3, + "overs": 9.1, + "isBatting": true, + "description": "" + } + ] + } + ] + } + ] + }, + { + "id": "1556696", + "name": "Brampton Blitz v Mississauga Masters", + "shortName": "BRB v MIM", + "date": "2026-10-03T05:45Z", + "status": { + "period": 0, + "session": null, + "summary": "Starts at 22:45 local time", + "type": { + "description": "Scheduled", + "detail": "Scheduled", + "shortDetail": "Scheduled", + "state": "pre" + } + }, + "competitions": [ + { + "date": "2026-10-03T05:45Z", + "class": { + "eventType": "ODI" + }, + "venue": { + "fullName": "BC Place, Vancouver" + }, + "competitors": [ + { + "order": 1, + "homeAway": "home", + "winner": "false", + "score": "", + "team": { + "id": "1506163", + "name": "Brampton Blitz", + "displayName": "Brampton Blitz", + "shortDisplayName": "BRB", + "abbreviation": "BRB" + }, + "linescores": [] + }, + { + "order": 2, + "homeAway": "away", + "winner": "false", + "score": "", + "team": { + "id": "1506165", + "name": "Mississauga Masters", + "displayName": "Mississauga Masters", + "shortDisplayName": "MIM", + "abbreviation": "MIM" + }, + "linescores": [] + } + ] + } + ] + } + ] + }, + "cricket:series_ids:bbl|international|ipl|australia|england|india": [ + { + "series_id": "22547", + "name": "Asian Games Men's Cricket Competition", + "competition_key": "international" + }, + { + "series_id": "24139", + "name": "Canada Super60", + "competition_key": "international" + } + ] +} \ No newline at end of file diff --git a/plugins/cricket-scoreboard/test/harness.json b/plugins/cricket-scoreboard/test/harness.json index b6f44f63..4d334100 100644 --- a/plugins/cricket-scoreboard/test/harness.json +++ b/plugins/cricket-scoreboard/test/harness.json @@ -1,5 +1,5 @@ { - "_comment": "Deterministic harness config + mocked ESPN cricket responses for golden-image rendering. mock_matches is consumed directly by test_cricket_plugin.py (the plugin's live fetcher is bypassed) and covers every format branch: a live T20 chase, a live Test with a session, a completed match, and an upcoming fixture.", + "_comment": "Deterministic harness config + mocked ESPN cricket responses. mock_data (test/fixtures/mock.json) seeds the plugin cache with a resolved series list and trimmed real ESPN scoreboards (a live T20 chase, a live Super60 innings, a result and two fixtures), so the core harness renders without touching the network. mock_matches is consumed directly by test_cricket_plugin.py (the plugin's live fetcher is bypassed) and covers every format branch: a live T20 chase, a live Test with a session, a completed match, and an upcoming fixture.", "config": { "enabled": true, "favorite_teams": ["India", "Australia", "England"], @@ -14,7 +14,8 @@ "show_venue": true, "display_modes": {"live": true, "recent": true, "upcoming": true} }, - "freeze_time": "2026-05-31 15:30:00", + "mock_data": "test/fixtures/mock.json", + "freeze_time": "2026-10-03 02:00:00", "mock_matches": [ { "id": "live-t20", diff --git a/plugins/f1-scoreboard/README.md b/plugins/f1-scoreboard/README.md index cccb5e89..d09d969d 100644 --- a/plugins/f1-scoreboard/README.md +++ b/plugins/f1-scoreboard/README.md @@ -102,7 +102,7 @@ Each mode section has an `enabled` toggle and mode-specific options: | `qualifying` | `show_team_duel` | `true` | Show team H2H summary card (who outqualified their teammate) | | `practice` | `sessions_to_show` | `["FP1","FP2","FP3"]` | Which sessions to render | | `practice` | `top_n` | `10` | Drivers per practice session | -| `sprint` | `top_finishers` | `10` | Sprint result depth | +| `sprint` | `top_finishers` | `10` | Sprint result depth (3–22) | | `calendar` | `max_events` | `5` | Race weekends to show | | `calendar` | `show_practice` | `false` | Include practice sessions in calendar | | `calendar` | `show_qualifying` | `true` | Include qualifying in calendar | @@ -196,12 +196,18 @@ Override the font used for each text role. All fonts are bundled in `assets/font | Key | Default font | Description | |---|---|---| -| `customization.header_text.font` | `PressStart2P-Regular.ttf` | Section headers and GP names | -| `customization.position_text.font` | `PressStart2P-Regular.ttf` | Position numbers and driver codes | -| `customization.detail_text.font` | `4x6-font.ttf` | Points, times, gaps | -| `customization.small_text.font` | `4x6-font.ttf` | Secondary info (circuit name, location) | +| `customization.header_text.font` | `""` (auto) | Section headers and GP names | +| `customization.position_text.font` | `""` (auto) | Position numbers and driver codes | +| `customization.detail_text.font` | `""` (auto) | Points, times, gaps | +| `customization.small_text.font` | `""` (auto) | Secondary info (circuit name, location) | -Available fonts: `PressStart2P-Regular.ttf`, `4x6-font.ttf`, `5by7.regular.ttf` +Left blank, each role gets a crisp bitmap font picked for your panel size +(`6x10.bdf` / `4x6.bdf` on 128x32, larger BDFs on bigger panels). + +Available fonts: `4x6.bdf`, `5x8.bdf`, `6x10.bdf`, `7x13.bdf`, `9x15.bdf`, +`10x20.bdf` (bitmap fonts, always drawn at their native size, so `font_size` +does not apply), and the scalable `PressStart2P-Regular.ttf`, `4x6-font.ttf`, +`5by7.regular.ttf`. Each role also takes a size, and `auto_scale` decides whether those sizes are used as written or scaled to the panel: @@ -209,10 +215,10 @@ used as written or scaled to the panel: | Key | Default | Description | |---|---|---| | `customization.auto_scale` | `true` | Leave the font fields below blank to auto-pick a crisp pixel (bitmap) font sized for your panel. This toggle only affects a scalable TTF you choose manually: on, it scales that TTF with panel size and snaps it to its pixel grid; off, it uses the exact size below. | -| `customization.header_text.font_size` | `8` | Size in pixels. | -| `customization.position_text.font_size` | `8` | Size in pixels. | -| `customization.detail_text.font_size` | `6` | Size in pixels. | -| `customization.small_text.font_size` | `6` | Size in pixels. | +| `customization.header_text.font_size` | `8` | Size in pixels (4–16; TTF fonts only). | +| `customization.position_text.font_size` | `8` | Size in pixels (4–16; TTF fonts only). | +| `customization.detail_text.font_size` | `6` | Size in pixels (4–16; TTF fonts only). | +| `customization.small_text.font_size` | `6` | Size in pixels (4–16; TTF fonts only). | ### Driver and team codes diff --git a/plugins/f1-scoreboard/f1_renderer.py b/plugins/f1-scoreboard/f1_renderer.py index d1185c29..b048380d 100644 --- a/plugins/f1-scoreboard/f1_renderer.py +++ b/plugins/f1-scoreboard/f1_renderer.py @@ -274,13 +274,19 @@ def _load_fonts(self) -> Dict[str, Any]: user_size = c.get("font_size") if font_name in _BDF_NATIVE_SIZE: size = _BDF_NATIVE_SIZE[font_name] # bitmap: always native px - elif user_size: - size = int(user_size) - else: - base = 8 if key in ("header", "position") else 6 + elif auto_scale: + # The web UI saves font_size with every config, so it is the + # base here rather than a bypass: auto_scale on scales it with + # the panel and snaps it to the font's pixel grid. + base = int(user_size) if user_size else ( + 8 if key in ("header", "position") else 6) floor = 6 if key in ("header", "position") else 5 target = max(floor, int(base * type_scale)) - size = _snap_font_size(font_name, target) if auto_scale else target + size = _snap_font_size(font_name, target) + else: + # auto_scale off: the exact size set. + size = int(user_size) if user_size else ( + 8 if key in ("header", "position") else 6) fonts[key] = self._load_font(font_name, int(size)) return fonts diff --git a/plugins/f1-scoreboard/manifest.json b/plugins/f1-scoreboard/manifest.json index 399d51cf..6d478626 100644 --- a/plugins/f1-scoreboard/manifest.json +++ b/plugins/f1-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "f1-scoreboard", "name": "F1 Scoreboard", - "version": "1.10.0", + "version": "1.10.1", "author": "ChuckBuilds", "class_name": "F1ScoreboardPlugin", "entry_point": "manager.py", @@ -29,6 +29,12 @@ "f1_calendar" ], "versions": [ + { + "version": "1.10.1", + "released": "2026-10-02", + "ledmatrix_min_version": "3.6.1", + "notes": "customization.auto_scale now does what it says for a TTF font you pick: on, the font size is scaled with the panel and snapped to the font's pixel grid; off, the exact size is used. The web UI saves font_size with every config, and a set font_size bypassed auto_scale entirely, while with no font_size auto_scale off still scaled. Blank font fields (the default bitmap fonts) are unchanged. README: the font table listed PressStart2P/4x6-font as defaults (the default is blank, auto-picked BDF) and omitted the six bundled BDF fonts; sprint.top_finishers range added." + }, { "version": "1.10.0", "released": "2026-09-29", @@ -352,7 +358,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/football-scoreboard/CHANGELOG.md b/plugins/football-scoreboard/CHANGELOG.md index 5e1a31bc..d70e5839 100644 --- a/plugins/football-scoreboard/CHANGELOG.md +++ b/plugins/football-scoreboard/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [3.18.5] - 2026-10-02 + +### Documentation +- README: the per-league `odds_update_interval` (`3600` s) and + `live_odds_update_interval` (`60` s) settings are documented in the + Update intervals table. No change in behaviour. + ## [3.18.4] - 2026-10-02 ### Changed diff --git a/plugins/football-scoreboard/README.md b/plugins/football-scoreboard/README.md index 89cd000e..01fa4240 100644 --- a/plugins/football-scoreboard/README.md +++ b/plugins/football-scoreboard/README.md @@ -518,6 +518,8 @@ All **Advanced**. | `.recent_update_interval` | 60–86400 s | `3600` | How often the finished-games list is rebuilt. This also sets how soon a game that has just ended can appear — lower it if you want results sooner. | | `.upcoming_update_interval` | 60–86400 s | `3600` | How often the upcoming-games list is rebuilt. Selection and the non-favorite rotation both run on the display side, so this governs only the fetch. | | `.stale_game_timeout` | 60–3600 s | `300` | Drop a live game the API has stopped updating. | +| `.odds_update_interval` | 60–86400 s | `3600` | How often odds are re-fetched for recent and upcoming games. Only used when `show_odds` is on. | +| `.live_odds_update_interval` | 15–3600 s | `60` | How often odds are re-fetched for a game in progress. Only used when `show_odds` is on. | ### Display options diff --git a/plugins/football-scoreboard/manifest.json b/plugins/football-scoreboard/manifest.json index e74b4fbc..d029cc13 100644 --- a/plugins/football-scoreboard/manifest.json +++ b/plugins/football-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "football-scoreboard", "name": "Football Scoreboard", - "version": "3.18.4", + "version": "3.18.5", "update_interval": 60, "author": "ChuckBuilds", "class_name": "FootballScoreboardPlugin", @@ -25,6 +25,12 @@ "ncaa_fb_live" ], "versions": [ + { + "version": "3.18.5", + "released": "2026-10-02", + "ledmatrix_min_version": "3.8.0", + "notes": "README only: the per-league odds_update_interval (default 3600 s) and live_odds_update_interval (default 60 s) settings are now documented in the Update intervals table. No change in behaviour." + }, { "version": "3.18.4", "released": "2026-10-02", diff --git a/plugins/hello-world/README.md b/plugins/hello-world/README.md index c862fb7e..a6a2bd56 100644 --- a/plugins/hello-world/README.md +++ b/plugins/hello-world/README.md @@ -105,14 +105,17 @@ thirds. With it off, the message alone is drawn on the centre line. The clock shows the LEDMatrix timezone from the main settings, falling back to the Pi's system time when that setting is missing or invalid. -**The message is not shrunk or wrapped.** It is drawn centred at whatever size -the font gives, so a message wider than the panel is clipped at both ends: +**The message is shrunk to fit, never wrapped.** Each line starts at its +registered font size (10 px for the message, 8 px for the clock) and steps down +a pixel at a time until it fits the panel width, stopping at 4 px. A message +still too wide at that size, or any message on an install without the core font +manager (the bundled 6x9 BDF is used unshrunk there), is clipped at both ends: ![Hi, Hello World! and a message too long to fit, on a 128x32 panel](../../docs/assets/hello-world/message-length.png) -The default `Hello, World!` just fits a 128-wide panel. On a 64-wide panel it -does not — keep the message short, or use the +The default `Hello, World!` fits a 128-wide panel at full size; on a 64-wide +panel it is drawn in a much smaller font. Keep the message short, or use the [Scrolling Text](../text-display/) plugin, which scrolls and can auto-size. ### Colours @@ -165,8 +168,8 @@ differ so the two lines read as separate things at a glance. panels](../../docs/assets/hello-world/panel-sizes.png) Text is centred horizontally and placed by fractions of the panel height, so -the layout holds at any size. The only real constraint is message width — see -above. +the layout holds at any size. The only real constraint is message width: a +narrow panel shrinks the font (see above). --- @@ -227,7 +230,8 @@ case-sensitive, no spaces. This is the single most common mistake when copying this plugin to start a new one. **The message is cut off at both ends.** -It is wider than the panel. The plugin centres but does not resize or wrap; use +It is wider than the panel even at the smallest font size the plugin steps +down to, or the core font manager is unavailable. The plugin does not wrap; use a shorter message or a wider panel. **Colours look wrong.** @@ -246,7 +250,8 @@ hello-world/ ├── manager.py # HelloWorldPlugin ├── config_schema.json # Settings schema; source of truth for defaults ├── example_config.json # A config block to copy -├── requirements.txt # None beyond the core +├── requirements.txt # freetype-py, which the core already installs +├── test_timezone.py # Regression test: the clock follows the LEDMatrix timezone ├── QUICK_START.md # Enabling it and verifying it on a Pi └── README.md ``` diff --git a/plugins/hello-world/manager.py b/plugins/hello-world/manager.py index 6ecf4d3b..01b94c81 100644 --- a/plugins/hello-world/manager.py +++ b/plugins/hello-world/manager.py @@ -77,11 +77,12 @@ def on_config_change(self, new_config): def _register_fonts(self): """Register fonts with the font manager.""" try: - if not hasattr(self.plugin_manager, 'font_manager'): + # The core always sets the attribute but passes None when it has + # no font manager, so hasattr() alone is not enough. + font_manager = getattr(self.plugin_manager, 'font_manager', None) + if font_manager is None: return - font_manager = self.plugin_manager.font_manager - # Message font font_manager.register_manager_font( manager_id=self.plugin_id, @@ -217,8 +218,8 @@ def display(self, force_clear=False): time_font = None try: - if hasattr(self.plugin_manager, 'font_manager'): - font_manager = self.plugin_manager.font_manager + font_manager = getattr(self.plugin_manager, 'font_manager', None) + if font_manager is not None: # resolve_font() is the accessor for a registered element -- # it honours user overrides and takes the same (family, # size_px) the element was registered with. get_font() is @@ -293,7 +294,8 @@ def display(self, force_clear=False): x=width // 2, y=height // 2, color=(255, 0, 0), - font=self.bdf_font + font=self.bdf_font, + centered=True ) self.display_manager.update_display() except Exception: diff --git a/plugins/hello-world/manifest.json b/plugins/hello-world/manifest.json index 5e9dfef9..482c93ba 100644 --- a/plugins/hello-world/manifest.json +++ b/plugins/hello-world/manifest.json @@ -1,7 +1,7 @@ { "id": "hello-world", "name": "Hello World", - "version": "1.1.3", + "version": "1.1.4", "author": "ChuckBuilds", "description": "A simple test plugin that displays a customizable message", "entry_point": "manager.py", @@ -16,6 +16,12 @@ "hello-world" ], "versions": [ + { + "version": "1.1.4", + "released": "2026-10-02", + "ledmatrix_min_version": "2.0.0", + "notes": "A core that has no font manager (the attribute set to None) no longer logs a font warning on every frame: the plugin checked hasattr(), which is true for None, then called resolve_font on None. The error screen's text is centred instead of starting mid-panel and running off the right edge. README: the message is shrunk to fit the panel width (down to 4 px) when the core font manager is present, not clipped at full size; the file list no longer says requirements.txt is empty (it lists freetype-py)." + }, { "version": "1.1.3", "released": "2026-09-28", @@ -55,7 +61,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/hockey-scoreboard/CHANGELOG.md b/plugins/hockey-scoreboard/CHANGELOG.md index 09210e0c..90e32e27 100644 --- a/plugins/hockey-scoreboard/CHANGELOG.md +++ b/plugins/hockey-scoreboard/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [1.42.2] - 2026-10-02 + +### Documentation +- README: documents `.update_intervals.live_odds` (`60` s) and the + full-screen `scroll_card.switch_show_date` / `switch_show_time` switches, + and says `show_date` / `show_time` apply to the scroll and Vegas cards. +- `requirements.txt`: dropped a stale note about a `base_classes.py` the + plugin no longer ships. No change in behaviour. + ## [1.42.1] - 2026-10-02 ### Changed diff --git a/plugins/hockey-scoreboard/README.md b/plugins/hockey-scoreboard/README.md index a6691018..391067e3 100644 --- a/plugins/hockey-scoreboard/README.md +++ b/plugins/hockey-scoreboard/README.md @@ -532,6 +532,7 @@ All **Advanced**. | `.update_intervals.recent` | 60–86400 s | `3600` | Refresh for finished games. | | `.update_intervals.upcoming` | 60–86400 s | `3600` | Refresh for the schedule. | | `.update_intervals.odds` | 60–86400 s | `3600` | Refresh for betting odds. | +| `.update_intervals.live_odds` | 30–3600 s | `60` | Refresh for betting odds while a game is live. | | `.update_intervals.stale_game_timeout` | 60–3600 s | `300` | Drop a live game the API has stopped updating. | ### Display durations @@ -617,7 +618,8 @@ Plugin-wide, not per league. | Date Format | `scroll_card.date_format` | `abbrev` | Scroll and Vegas cards: `Sep 19`, `9/19`, `19 Sep`, `19/9`, or `Fri Sep 19`. | | Full-Screen Date Format | `scroll_card.switch_date_format` | `numeric` | **Advanced.** The same for the full-screen scoreboard, plus `inherit`. It has its own default because the two displays disagree about what is normal. | | Time Format | `scroll_card.time_format` | `12h` | 12- or 24-hour clock. | -| Show Date / Show Time | `scroll_card.show_date`, `scroll_card.show_time` | `true` | Drop either line. | +| Show Date / Show Time | `scroll_card.show_date`, `scroll_card.show_time` | `true` | Drop either line from the scroll and Vegas cards. | +| Full-Screen Show Date / Show Time | `scroll_card.switch_show_date`, `scroll_card.switch_show_time` | `true` | The same for the full-screen upcoming scoreboard. Separate switches because Show Date / Show Time predate that display reading the block. | | Full-Screen Recent Date | `scroll_card.switch_recent_show_date` | `true` | Draw the date a finished game was played along the bottom of the full-screen recent scoreboard, written in the Full-Screen Date Format. | | Swap Date and Time | `scroll_card.swap_date_time` | `false` | Flip the two lines. Each display starts from its own order, so this flips rather than forces. | diff --git a/plugins/hockey-scoreboard/manifest.json b/plugins/hockey-scoreboard/manifest.json index a814f355..3e42c2e2 100644 --- a/plugins/hockey-scoreboard/manifest.json +++ b/plugins/hockey-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "hockey-scoreboard", "name": "Hockey Scoreboard", - "version": "1.42.1", + "version": "1.42.2", "author": "ChuckBuilds", "description": "Live, recent, and upcoming hockey games across NHL, NCAA Men's, and NCAA Women's hockey with real-time scores and schedules", "homepage": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/hockey-scoreboard", @@ -54,6 +54,12 @@ } ], "versions": [ + { + "version": "1.42.2", + "released": "2026-10-02", + "ledmatrix_min_version": "3.8.0", + "notes": "README only: documents update_intervals.live_odds (default 60 s) and the full-screen scroll_card.switch_show_date / switch_show_time switches, and says Show Date / Show Time apply to the scroll and Vegas cards. requirements.txt drops a stale note about a base_classes.py the plugin no longer ships. No change in behaviour." + }, { "version": "1.42.1", "released": "2026-10-02", diff --git a/plugins/hockey-scoreboard/requirements.txt b/plugins/hockey-scoreboard/requirements.txt index 3b846ac4..8fdee5fc 100644 --- a/plugins/hockey-scoreboard/requirements.txt +++ b/plugins/hockey-scoreboard/requirements.txt @@ -18,6 +18,3 @@ urllib3>=2.7.0 # - src.cache_manager (CacheManager) # - src.display_manager (DisplayManager) -# Note: The plugin now includes its own base classes in base_classes.py -# to be self-contained and not depend on the main LEDMatrix installation - diff --git a/plugins/incoming-packages/README.md b/plugins/incoming-packages/README.md index 4f428a1f..543ed9e7 100644 --- a/plugins/incoming-packages/README.md +++ b/plugins/incoming-packages/README.md @@ -134,7 +134,7 @@ under `incoming-packages`. The full schema is | `include_delivered` | `false` | Also give already-delivered packages their own card | | `show_usps_mail_image` | `true` | Include the USPS Informed Delivery mail card when there is mail | | `show_delivery_images` | `true` | Show a carrier's scanned delivery photo when it is out for delivery today | -| `highlight_today` | `true` | Sort arriving-today carriers first and accent their count | +| `highlight_today` | `true` | Give the arriving-today count its own line in `accent_color`. Off drops that line from the count and summary cards. Carriers arriving today are sorted first either way | | `accent_color` | `[0, 220, 120]` | The accent colour used for that highlight | | `customization.title_text.text_color` | `[255, 255, 255]` | Colour of the primary text | @@ -161,7 +161,7 @@ compact summary: ![show_dashboard true and false](../../docs/assets/incoming-packages/show-dashboard.png) -`highlight_today` controls both the accent colour and the sort order: +`highlight_today` decides whether the arriving-today count gets its own accented line. With it off the card falls back to the in-transit count; the sort order does not change: ![highlight_today true and false](../../docs/assets/incoming-packages/highlight-today.png) diff --git a/plugins/incoming-packages/manifest.json b/plugins/incoming-packages/manifest.json index 697610ba..980dac68 100644 --- a/plugins/incoming-packages/manifest.json +++ b/plugins/incoming-packages/manifest.json @@ -1,7 +1,7 @@ { "id": "incoming-packages", "name": "Incoming Packages", - "version": "1.1.3", + "version": "1.1.4", "author": "ChuckBuilds", "description": "Rotating cards for packages headed your way - per carrier, with the count arriving today highlighted. Reads a normalized snapshot from a pluggable provider: the Home Assistant Mail and Packages integration (default), AfterShip, or a built-in demo. Email scanning stays inside Home Assistant; the plugin only stores an API URL + token.", "category": "productivity", @@ -27,6 +27,12 @@ "pillow" ], "versions": [ + { + "released": "2026-10-02", + "version": "1.1.4", + "changelog": "README only: highlight_today does not change the sort order (arriving-today carriers always sort first); turning it off drops the accented arriving-today line.", + "ledmatrix_min_version": "3.4.0" + }, { "released": "2026-09-28", "version": "1.1.3", @@ -76,7 +82,7 @@ "notes": "Initial release. Provider-agnostic incoming-package display with a Home Assistant Mail and Packages provider (default, auto-discovers sensor.mail_* entities over the HA REST API), an AfterShip provider, and a built-in demo provider. Size-adaptive rotating cards: one per active carrier with a drawn carrier badge, the count arriving today (prioritized and shown in an accent color), the count in transit, and a lead summary card, plus optional USPS mail count. Renders on all panel sizes from 64x32 to 256x64 with adaptive font tiers and marquee text; on-panel setup/error messages." } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/jellyfin-now-playing/CHANGELOG.md b/plugins/jellyfin-now-playing/CHANGELOG.md index d08b0975..f13e83d5 100644 --- a/plugins/jellyfin-now-playing/CHANGELOG.md +++ b/plugins/jellyfin-now-playing/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.3.3] - 2026-10-02 + +### Fixed +- The subtitle font size falls back to 7 when the setting is absent: the + schema default, and the 4x6 font's crisp size. The code fell back to 6, + which drops glyph columns. The README listed 6 as the default; it says 7. + ## [1.3.2] - 2026-09-28 ### Fixed diff --git a/plugins/jellyfin-now-playing/README.md b/plugins/jellyfin-now-playing/README.md index 9323bc63..9c1a730b 100644 --- a/plugins/jellyfin-now-playing/README.md +++ b/plugins/jellyfin-now-playing/README.md @@ -75,7 +75,7 @@ Customization** in the web UI. | `customization.title_text.font_size` | `7` | Title height in pixels, 4–16 (advanced) | | `customization.title_text.text_color` | `[255, 255, 255]` | Title color | | `customization.subtitle_text.font` | `4x6-font.ttf` | Font for the subtitle and the time readout (advanced) | -| `customization.subtitle_text.font_size` | `6` | Subtitle height in pixels, 4–16 (advanced) | +| `customization.subtitle_text.font_size` | `7` | Subtitle height in pixels, 4–16 (advanced) | | `customization.subtitle_text.text_color` | `[170, 170, 170]` | Subtitle color | | `customization.progress_bar.bar_color` | `[124, 77, 255]` | Filled portion of the bar. Ignored while paused, when the bar is amber | | `customization.progress_bar.background_color` | `[40, 40, 40]` | Unfilled portion of the bar | diff --git a/plugins/jellyfin-now-playing/manager.py b/plugins/jellyfin-now-playing/manager.py index afcb7073..174dfc75 100644 --- a/plugins/jellyfin-now-playing/manager.py +++ b/plugins/jellyfin-now-playing/manager.py @@ -90,7 +90,7 @@ def __init__(self, plugin_id: str, config: Dict[str, Any], self.title_font = self._load_font(title_text.get('font', '5by7.regular.ttf'), int(title_text.get('font_size', 7))) self.subtitle_font = self._load_font(subtitle_text.get('font', '4x6-font.ttf'), - int(subtitle_text.get('font_size', 6))) + int(subtitle_text.get('font_size', 7))) # State self.now_playing: Optional[Dict[str, Any]] = None diff --git a/plugins/jellyfin-now-playing/manifest.json b/plugins/jellyfin-now-playing/manifest.json index 8fd6c25c..757425e7 100644 --- a/plugins/jellyfin-now-playing/manifest.json +++ b/plugins/jellyfin-now-playing/manifest.json @@ -1,7 +1,7 @@ { "id": "jellyfin-now-playing", "name": "Jellyfin Now Playing", - "version": "1.3.2", + "version": "1.3.3", "author": "ChuckBuilds", "description": "Shows what's playing on your Jellyfin server: poster art, title, and playback progress", "category": "media", @@ -26,6 +26,12 @@ "pillow" ], "versions": [ + { + "released": "2026-10-02", + "version": "1.3.3", + "changelog": "The subtitle font size falls back to 7, the schema default and the 4x6 font's crisp size, when the setting is absent; the code fell back to 6, which drops glyph columns. README default corrected to match.", + "ledmatrix_min_version": "3.4.0" + }, { "released": "2026-09-28", "version": "1.3.2", @@ -87,7 +93,7 @@ "notes": "Initial release: polls the Jellyfin /Sessions API and shows the active session's poster (series poster for TV episodes), scrolling title and subtitle, a paused indicator, and a progress bar that moves smoothly between polls. Includes username and content-type filters, a Nothing Playing screen, and on-panel setup/error messages on all four panel sizes." } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/lacrosse-scoreboard/CHANGELOG.md b/plugins/lacrosse-scoreboard/CHANGELOG.md index a8510e90..a88882c5 100644 --- a/plugins/lacrosse-scoreboard/CHANGELOG.md +++ b/plugins/lacrosse-scoreboard/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [1.36.3] - 2026-10-02 + +### Changed +- `defaults.game_display_duration` is hidden: nothing reads it (per-game time + comes from each league's `display_durations`; the value only appeared in the + plugin's status info). Kept declared so saved configs keep validating. +- README documents `defaults.game_display_duration`, + `.update_intervals.live_odds` and `scroll_card.switch_show_date` / + `switch_show_time`. + ## [1.36.2] - 2026-10-02 ### Changed diff --git a/plugins/lacrosse-scoreboard/README.md b/plugins/lacrosse-scoreboard/README.md index 25c7e366..5fc96fc7 100644 --- a/plugins/lacrosse-scoreboard/README.md +++ b/plugins/lacrosse-scoreboard/README.md @@ -238,6 +238,7 @@ Fallbacks used when the corresponding per-league setting is absent. | Key | Type | Default | What it does | |---|---|---|---| | `defaults.display_duration` | 5–60 s | `15` | Per-game on-screen time. | +| `defaults.game_display_duration` | 3–60 s | `15` | **Hidden (still declared); has no effect.** Per-game time comes from each league's `display_durations`. | | `defaults.show_records` | boolean | `false` | Draw win-loss records. | | `defaults.show_ranking` | boolean | `false` | Draw poll rank badges. | | `defaults.show_odds` | boolean | `false` | **Advanced.** Draw betting odds. | @@ -311,6 +312,7 @@ All **Advanced**. | `.update_intervals.recent` | 60–86400 s | `3600` | Refresh for finished games. | | `.update_intervals.upcoming` | 60–86400 s | `3600` | Refresh for the schedule. | | `.update_intervals.odds` | 60–86400 s | `3600` | Refresh for betting odds. | +| `.update_intervals.live_odds` | 30–3600 s | `60` | **Advanced.** Refresh for betting odds while a game is live. | | `.update_intervals.stale_game_timeout` | 60–3600 s | `300` | Drop a live game the API has stopped updating. | ### Display durations @@ -427,7 +429,8 @@ These settings are plugin-wide, not per league, and apply to every display mode. | Date Format | `scroll_card.date_format` | `abbrev` | Scroll and Vegas cards: `Sep 19`, `9/19`, `19 Sep`, `19/9`, or `Fri Sep 19`. | | Full-Screen Date Format | `scroll_card.switch_date_format` | `numeric` | **Advanced.** The same for the full-screen scoreboard, plus `inherit`. It has its own default because the two displays disagree about what is normal. | | Time Format | `scroll_card.time_format` | `12h` | 12- or 24-hour clock. | -| Show Date / Show Time | `scroll_card.show_date`, `scroll_card.show_time` | `true` | Drop either line. | +| Show Date / Show Time | `scroll_card.show_date`, `scroll_card.show_time` | `true` | Drop either line from the scroll and Vegas cards. | +| Full-Screen Show Date / Show Time | `scroll_card.switch_show_date`, `scroll_card.switch_show_time` | `true` | The same for the full-screen scoreboard. Separate switches because the originals predate this display reading the block, and sharing them would have changed what existing boards draw. | | Full-Screen Recent Date | `scroll_card.switch_recent_show_date` | `true` | Draw the date a finished game was played along the bottom of the full-screen recent scoreboard, written in the Full-Screen Date Format. | | Swap Date and Time | `scroll_card.swap_date_time` | `false` | Flip the two lines. Each display starts from its own order, so this flips rather than forces. | diff --git a/plugins/lacrosse-scoreboard/config_schema.json b/plugins/lacrosse-scoreboard/config_schema.json index a4f82310..7550470f 100644 --- a/plugins/lacrosse-scoreboard/config_schema.json +++ b/plugins/lacrosse-scoreboard/config_schema.json @@ -217,8 +217,9 @@ "default": 15, "minimum": 3, "maximum": 60, - "description": "Duration in seconds to show each individual game before rotating to the next game within the same mode", - "x-advanced": true + "description": "Duration in seconds to show each individual game before rotating to the next game within the same mode HIDDEN: nothing reads it. Per-game time comes from each league's display_durations (live/recent/upcoming); this value only appears in the plugin's status info. Kept declared so saved configs keep validating.", + "x-advanced": true, + "x-display": "hidden" }, "show_records": { "type": "boolean", diff --git a/plugins/lacrosse-scoreboard/manifest.json b/plugins/lacrosse-scoreboard/manifest.json index 38213534..b3750197 100644 --- a/plugins/lacrosse-scoreboard/manifest.json +++ b/plugins/lacrosse-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "lacrosse-scoreboard", "name": "Lacrosse Scoreboard", - "version": "1.36.2", + "version": "1.36.3", "author": "ChuckBuilds", "description": "Live, recent, and upcoming NCAA men's and women's lacrosse games with real-time scores and schedules", "homepage": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/lacrosse-scoreboard", @@ -50,6 +50,12 @@ } ], "versions": [ + { + "version": "1.36.3", + "released": "2026-10-02", + "ledmatrix_min_version": "3.8.0", + "notes": "Hides defaults.game_display_duration, which nothing reads (per-game time comes from each league's display_durations). README documents it, update_intervals.live_odds and scroll_card.switch_show_date / switch_show_time. No change in behaviour." + }, { "version": "1.36.2", "released": "2026-10-02", diff --git a/plugins/ledmatrix-elections/README.md b/plugins/ledmatrix-elections/README.md index 8f08813c..e1018b1f 100644 --- a/plugins/ledmatrix-elections/README.md +++ b/plugins/ledmatrix-elections/README.md @@ -106,9 +106,10 @@ testing or to point at one specific feed: | `only_my_state` | `true` | National + your state's races only. | | `race_types` | `["president","senate","governor","house","ballot"]` | Office types to show. | | `update_interval` | `60` | Poll seconds. Use 30–60 on election night. | -| `display_duration` | `30` | Ticker seconds per rotation (dynamic duration may extend). | +| `display_duration` | `30` | Fallback only. While races are loaded the ticker stays for exactly one pass over every race (at least 6 s), however long that is; a duration set for the mode on the web UI's rotation page overrides both. | | `hide_called_after_seconds` | `86400` | Drop a called race from the ticker this long after it was called (0 disables). | | `live_priority` | `true` | Allow the called-race interrupt. | +| `interrupt.enabled` | `true` | Interrupt with the full-screen card when a race is newly called. Off: a new call no longer takes over the screen. | | `interrupt.duration_seconds` | `12` | How long each called card holds the screen. | | `interrupt.my_state_only` | `true` | Only interrupt for races passing the state filter. | | `interrupt.max_age_seconds` | `300` | Drop stale queued calls older than this. | @@ -119,7 +120,7 @@ testing or to point at one specific feed: | `override.feed_filename` | `""` | Exact NYT filename for a non-standard slug (specials). | | `override.feed_url` | `""` | Full URL of a results JSON; bypasses URL building entirely. | | `override.trail_days` | `14` | Days after election day to keep showing results. | -| `calendar_events` | `[]` | Extra scheduled elections (e.g. your state's primary). Each: `{state,date,type,feed_filename?,trail_days?}`. | +| `calendar_events` | `[]` | Extra scheduled elections (e.g. your state's primary). Each: `{state,date,type,feed_filename?,trail_days?}`; `feed_url` is also read, by hand-editing `config.json`. `date` must be `YYYY-MM-DD`; an entry that is not is skipped with a warning. | | `local_races` | `true` | Use your state's authoritative local results source where one exists (auto-engages by state; currently CA). | | `test_mode` | `false` | Render bundled fixtures offline (demo / no election night). | @@ -129,24 +130,26 @@ These are real settings the table above left out. | Key | Default | Notes | |---|---|---| -| `lower_chamber_district` | `""` | Your state legislature lower-chamber district number — whatever your state calls it (Assembly, House of Delegates). Set it to have that local race appear in the ticker; leave blank to omit it. | -| `upper_chamber_district` | `""` | Your state senate district number. Same idea. | +| `lower_chamber_district` | `""` | Your state legislature lower-chamber district number — whatever your state calls it (Assembly, House of Delegates). Set it to have that local race appear in the ticker; leave blank to omit it. `assembly_district` is read as an alias. | +| `upper_chamber_district` | `""` | Your state senate district number. Same idea. `senate_district` is read as an alias. | | `scroll_speed` | `1.0` | Pixels moved per scroll step. The ticker runs at `scroll_speed / scroll_delay` pixels per second (default 33.3), moved by the LEDMatrix core to the nearest speed the panel can draw in whole pixels. | | `scroll_delay` | `0.03` | Seconds per scroll step; lower is faster. Around `0.03` reads comfortably, `0.01` is brisk. | | `providers.nyt.enabled` | `true` | Use the NYT static feed as the national baseline. | | `providers.nyt.base_url` | `https://static01.nyt.com/elections-assets/pages/data` | Where the NYT feeds are fetched from. Change it only to point at a mirror or a local capture. | | `providers.nyt.election_date` | `2026-06-02` | Drives the feed URL. Normally set for you by the calendar or `override` — see the note below. | -| `providers.nyt.election_type` | `primary` | `primary` feeds are per-state; `general` feeds are national. Also normally set for you. | +| `providers.nyt.election_type` | `primary` | Picks the feed name: `results-{state}-primary.json` for `primary`, `results-{state}.json` for `general` (both per-state). Also normally set for you. | | `providers.ca_sos.base_url` | `https://api.sos.ca.gov/returns` | California Secretary of State returns API. | -| `providers.ca_sos.offices` | `[]` | Statewide office slugs to fetch from the SoS (e.g. `governor`, `attorney-general`). Empty uses the built-in set. | +| `providers.ca_sos.offices` | `[]` | Statewide office slugs to fetch from the SoS (e.g. `governor`, `attorney-general`). Empty fetches none. | | `providers.ca_sos.include_house` | `false` | Also pull CA U.S. House results from the SoS. Off by default — the national feed already covers them. | | `providers.ca_sos.called_threshold` | `99.0` | Reporting percentage at which an SoS office counts as called, since the SoS feed carries no winner flag. | -> **Two settings the code reads are not in the schema**, so they never appear in -> the web UI and can only be set by hand-editing `config.json`: -> `test_mode` (default `false`, used above to render bundled fixtures offline) -> and `providers.ca_sos.override_nyt_votes` (default `true`, described under -> California local results). Both work; they are simply not offered by the form. +> **Three settings the code reads are not in the schema**, so they never appear +> in the web UI and can only be set by hand-editing `config.json`: +> `test_mode` (default `false`, used above to render bundled fixtures offline), +> `providers.ca_sos.override_nyt_votes` (default `true`, described under +> California local results) and `providers.ca_sos.advance_count` (default `2`, +> how many leading candidates count as advancing once an SoS race is called). +> All work; they are simply not offered by the form. > The active election's date and type are chosen by the calendar/override and > pushed to the NYT provider automatically; you don't set `providers.nyt.election_date` @@ -154,10 +157,13 @@ These are real settings the table above left out. ### California local results -For California users this **engages automatically** (`local_races` is on by -default) — there's no provider to switch on. It supplements CA races with the -authoritative Secretary of State tally and an optional **county/city rollup**. -The knobs under `providers.ca_sos` only fine-tune that rollup: +For California users the Secretary of State source **engages automatically** +(`local_races` is on by default) — there's no provider to switch on. Out of the +box it fetches nothing, though: the NYT feed already carries CA's statewide, +U.S. House and legislature races with a better reporting estimate. It only +contributes once you list `providers.ca_sos.offices` or turn on +`providers.ca_sos.include_house`; it then supplements those CA races with the +authoritative SoS tally, optionally as a **county/city rollup**: - `county` — a county slug (e.g. `los-angeles`) for county-level totals. - `city` — a major CA city, mapped to its county (ignored if `county` is set). @@ -191,7 +197,7 @@ To add a state/source: 1. Create `providers/.py` with a class extending `ElectionProvider`. Implement `fetch(state)` → `list[Race]` and `provides_states()` (return the set of states it supplements, or `None` for a national baseline). -2. Normalize into the common `Race`/`Candidate` model (`data_model.py`). Use +2. Normalize into the common `Race`/`Candidate` model (`election_data_model.py`). Use `make_race_id(office, state, district)` so your races merge with the baseline. 3. Register it in `create_providers()` (`providers/__init__.py`), gated on its own `providers..enabled` config flag. diff --git a/plugins/ledmatrix-elections/config_schema.json b/plugins/ledmatrix-elections/config_schema.json index e1c7112f..cd9dcc38 100644 --- a/plugins/ledmatrix-elections/config_schema.json +++ b/plugins/ledmatrix-elections/config_schema.json @@ -12,7 +12,7 @@ "display_duration": { "type": "number", "title": "Ticker Duration (seconds)", - "description": "How long the scrolling ticker shows per rotation. Dynamic duration may extend this to fit all races.", + "description": "Fallback ticker duration. While races are loaded the ticker stays for exactly one pass over every race (at least 6 seconds), however long that takes.", "minimum": 10, "maximum": 300, "default": 30 diff --git a/plugins/ledmatrix-elections/manager.py b/plugins/ledmatrix-elections/manager.py index 9d6789e4..9833c236 100644 --- a/plugins/ledmatrix-elections/manager.py +++ b/plugins/ledmatrix-elections/manager.py @@ -316,14 +316,19 @@ def _config_events(self) -> List[ElectionEvent]: out: List[ElectionEvent] = [] for e in self.calendar_events_cfg: try: - out.append(ElectionEvent( + ev = ElectionEvent( state=(e.get("state") or self.state or "").upper(), date=e["date"], type=e.get("type", "general"), trail_days=int(e.get("trail_days", 14)), feed_filename=(e.get("feed_filename") or None), feed_url=(e.get("feed_url") or None), - )) + ) + # Parse the date here: a malformed one (e.g. "11/03/2026") + # otherwise raised later in resolve_active, failing every + # update so no election -- built-in or configured -- showed. + ev.election_day() + out.append(ev) except Exception: self.logger.warning("Elections: ignoring bad calendar_events entry: %s", e) return out diff --git a/plugins/ledmatrix-elections/manifest.json b/plugins/ledmatrix-elections/manifest.json index 144f7a18..0d042a7f 100644 --- a/plugins/ledmatrix-elections/manifest.json +++ b/plugins/ledmatrix-elections/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-elections", "name": "Election Results", - "version": "1.3.1", + "version": "1.3.2", "author": "rpierce99", "description": "Live election results: a scrolling ticker of important races plus a full-screen interrupt when a race is newly called. Auto-activates for your state's primary + general (dormant otherwise), drains stale calls from the ticker, and survives restarts. Filterable to your state. NYT baseline provider + optional California SoS county/city rollup.", "entry_point": "manager.py", @@ -22,6 +22,12 @@ "cache_manager" ], "versions": [ + { + "version": "1.3.2", + "released": "2026-10-02", + "ledmatrix_min_version": "3.4.0", + "notes": "The called-race snapshot now survives a restart of any length, as the README says. It was read back with the cache's default five-minute max_age, so after a longer outage it was gone: calls made while down never interrupted, and races called after election day were re-dated to election day and dropped from the ticker at once. A calendar_events entry whose date is not YYYY-MM-DD is now skipped with a warning; it used to make every update raise, so no election showed at all. Docs: display_duration is described as the fallback it is (the ticker runs one full pass of every race), interrupt.enabled, the district aliases, calendar_events feed_url and providers.ca_sos.advance_count are documented, an empty providers.ca_sos.offices fetches nothing (not a built-in set), general NYT feeds are per-state, the California SoS source contributes nothing until offices or include_house is set, and the data-model file is election_data_model.py. The render-harness config now freezes the clock on election night: the fixture calls are dated to election day, so since June the 24-hour hide had dropped them all and the election_called check was drawing the ticker instead. Requires LEDMatrix core 3.4.0, as before." + }, { "version": "1.3.1", "released": "2026-09-16", @@ -97,7 +103,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2026-09-16", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": false, diff --git a/plugins/ledmatrix-elections/store.py b/plugins/ledmatrix-elections/store.py index abb430b3..5fea450b 100644 --- a/plugins/ledmatrix-elections/store.py +++ b/plugins/ledmatrix-elections/store.py @@ -48,7 +48,12 @@ def set_election(self, key: Optional[str]) -> None: self._called_at = {} if self.cache_manager and key: try: - cached = self.cache_manager.get(f"elections_snapshot_{key}") + # max_age=None: the core's get() otherwise expires an entry + # after 300s, so a restart more than five minutes after the + # last write lost the snapshot -- calls made while down never + # interrupted, and later calls were re-dated to election day + # and hidden from the ticker at once. + cached = self.cache_manager.get(f"elections_snapshot_{key}", max_age=None) except Exception: cached = None if cached: diff --git a/plugins/ledmatrix-elections/test/harness.json b/plugins/ledmatrix-elections/test/harness.json index 1e73705e..08157a99 100644 --- a/plugins/ledmatrix-elections/test/harness.json +++ b/plugins/ledmatrix-elections/test/harness.json @@ -1,5 +1,6 @@ { - "_comment": "Deterministic config for the plugin safety harness. test_mode parses the bundled June-2-2026 primary fixtures offline (no network), so both the ticker and called-card screens render identically on any host for golden-image comparison.", + "_comment": "Deterministic config for the plugin safety harness. test_mode parses the bundled June-2-2026 primary fixtures offline (no network), so both the ticker and called-card screens render identically on any host for golden-image comparison. freeze_time puts the clock on election night: the fixture calls are dated to election day, and hide_called_after_seconds (24h) would otherwise drop every one, leaving election_called to fall back to the ticker.", + "freeze_time": "2026-06-02 20:00:00", "config": { "enabled": true, "test_mode": true, diff --git a/plugins/ledmatrix-elections/test_calendar.py b/plugins/ledmatrix-elections/test_calendar.py index 43692dc1..16308c3a 100644 --- a/plugins/ledmatrix-elections/test_calendar.py +++ b/plugins/ledmatrix-elections/test_calendar.py @@ -172,10 +172,16 @@ def test_cold_start_seed(): # --------------------------------------------------------------------------- class FakeCache: + """Models the core's expiry: get() defaults to max_age=300 and an entry + older than that reads as a miss. ``age`` is how old every entry is.""" + def __init__(self): self.d = {} + self.age = 0.0 - def get(self, k): + def get(self, k, max_age=300): + if max_age is not None and self.age > max_age: + return None return self.d.get(k) def set(self, k, v): @@ -207,6 +213,15 @@ def test_persistence(): check(len(fired) == 1 and fired[0].id == "u-s-house-ca-12", "a fresh call after restart is still detected") + # A restart after a long outage (the snapshot is hours old) still loads + # it: the core's get() expires entries after 300s unless told otherwise. + cache.age = 6 * 3600 + s3 = RaceStore(cache_manager=cache) + s3.set_election(key) + check(s3._called_snapshot is not None and s3._called_at.get("u-s-house-ca-12") == 2100.0, + "a snapshot older than the cache's default max_age survives a restart") + cache.age = 0.0 + # Switching elections resets the snapshot. s2.set_election("CA_2026-11-03_general") check(s2._called_snapshot is None, "switching election resets the snapshot") @@ -293,6 +308,17 @@ def test_manager_resolution(): check(p._resolve_active(_ts("2026-03-25")) is None, "dormant again once the extra event's window passes") + # A malformed date in one entry is skipped, not fatal to the rest. + p = _plugin({"state": "TX", "calendar_events": [ + {"state": "TX", "date": "03/03/2026", "type": "primary"}, + {"state": "TX", "date": "2026-03-03", "type": "primary"}]}) + try: + ev = p._resolve_active(_ts("2026-03-05")) + ok = ev is not None and ev.date == "2026-03-03" + except Exception: + ok = False + check(ok, "a calendar_events entry with a bad date is ignored, the good one still resolves") + def test_local_provider_autoengage(): print("[Local provider auto-engage]") diff --git a/plugins/ledmatrix-leaderboard/README.md b/plugins/ledmatrix-leaderboard/README.md index b8578dc0..18442462 100644 --- a/plugins/ledmatrix-leaderboard/README.md +++ b/plugins/ledmatrix-leaderboard/README.md @@ -161,7 +161,7 @@ The full key list is in the [`enabled_sports`](#enabled_sports) table below. | `global.display.scroll_speed` | `1.0` | Pixels moved per scroll step (0.5–5.0). Speed is `scroll_speed / scroll_delay` px/s — see [Scroll speed](#scroll-speed). | | `global.display.scroll_delay` | `0.01` | Seconds per scroll step (0.001–0.1). | | `global.scroll_mode` | `"one_shot"` | Scrolling mode — one of `one_shot`, `continuous`. | -| `global.loop` | `false` | Continuously loop the leaderboard. | +| `global.loop` | `false` | Continuously loop the leaderboard. On, it overrides `scroll_mode` `one_shot`; `scroll_mode` `continuous` loops whatever this is set to. | | `global.appearance.pixel_perfect_text` | `true` | Render text with hard pixel edges. Disable only if you prefer the older anti-aliased (softer, blurrier) look. | | `global.appearance.crisp_logos` | `true` | Give logos hard edges instead of a ring of half-lit pixels. | | `global.appearance.text_outline` | `true` | Draw a black outline around text so it stays readable over logos. | diff --git a/plugins/ledmatrix-leaderboard/manifest.json b/plugins/ledmatrix-leaderboard/manifest.json index 3a506635..7179ada9 100644 --- a/plugins/ledmatrix-leaderboard/manifest.json +++ b/plugins/ledmatrix-leaderboard/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-leaderboard", "name": "Sports Leaderboard", - "version": "1.5.4", + "version": "1.5.5", "description": "Displays scrolling leaderboards and standings for multiple sports leagues including NFL, NBA, MLB, NCAA Football, NCAA Basketball, and more", "author": "ChuckBuilds", "entry_point": "manager.py", @@ -31,6 +31,12 @@ "requirements_file": "requirements.txt", "min_ledmatrix_version": "3.4.0", "versions": [ + { + "version": "1.5.5", + "released": "2026-10-02", + "ledmatrix_min_version": "3.4.0", + "notes": "README only: global.loop on overrides scroll_mode one_shot, as the settings form says." + }, { "version": "1.5.4", "released": "2026-09-28", @@ -151,7 +157,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "compatible_versions": [ ">=3.4.0" ] diff --git a/plugins/ledmatrix-music/CHANGELOG.md b/plugins/ledmatrix-music/CHANGELOG.md index 93be0b57..8a3344b6 100644 --- a/plugins/ledmatrix-music/CHANGELOG.md +++ b/plugins/ledmatrix-music/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [1.5.4] - 2026-10-02 + +### Fixed +- Spotify: the decoded album cover is kept between polls. The previous art URL + was read from the freshly replaced track dict, where it never exists, so + every progress-only poll (every 2 s) dropped the cover and display() + decoded it again. +- The `enabled` fallback is true, matching the schema and the core. + ## [1.5.3] - 2026-10-02 ### Fixed diff --git a/plugins/ledmatrix-music/manager.py b/plugins/ledmatrix-music/manager.py index d4a66a4e..ec22a508 100644 --- a/plugins/ledmatrix-music/manager.py +++ b/plugins/ledmatrix-music/manager.py @@ -211,7 +211,8 @@ def _load_config(self): try: # Flattened config access - no nested 'music' key - self.enabled = self.config.get("enabled", False) + # Same default as the schema and BasePlugin. + self.enabled = self.config.get("enabled", True) if not self.enabled: self.logger.info("Music plugin is disabled in config.") return @@ -833,7 +834,13 @@ def _poll_music_data(self): self.logger.debug("Polling Spotify: Significant change (title/artist/art/is_playing) detected.") else: self.logger.debug("Polling Spotify: Only progress changed, not significant.") - + + # Read before the swap below. It used to be read + # from the new dict, which never has it, so every + # progress-only poll discarded the decoded cover + # and display() decoded it again. + old_album_art_url = (self.current_track_info.get('album_art_url') + if self.current_track_info else None) self.current_track_info = simplified_info_poll self.current_source = MusicSource.SPOTIFY significant_change_for_callback = significant_change_detected @@ -847,7 +854,6 @@ def _poll_music_data(self): self.logger.debug("Polling Spotify: Minor update (progress only), no full refresh needed.") # Handle album art for Spotify - old_album_art_url = self.current_track_info.get('album_art_url_prev_spotify') new_album_art_url = simplified_info_poll.get('album_art_url') if new_album_art_url != old_album_art_url: self.album_art_image = None @@ -855,7 +861,6 @@ def _poll_music_data(self): # Downloaded at the end of this poll cycle by # _prefetch_current_album_art(), outside # track_info_lock so display() never waits on it. - self.current_track_info['album_art_url_prev_spotify'] = new_album_art_url self.logger.debug(f"Polling Spotify: Active track - {spotify_track.get('item', {}).get('name')}") else: diff --git a/plugins/ledmatrix-music/manifest.json b/plugins/ledmatrix-music/manifest.json index 6c2053da..e838c0ad 100644 --- a/plugins/ledmatrix-music/manifest.json +++ b/plugins/ledmatrix-music/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-music", "name": "Music Player - Now Playing", - "version": "1.5.3", + "version": "1.5.4", "description": "Real-time now playing display for Spotify and YouTube Music with album art, scrolling text, and progress bars", "author": "ChuckBuilds", "entry_point": "manager.py", @@ -65,6 +65,12 @@ } ], "versions": [ + { + "version": "1.5.4", + "released": "2026-10-02", + "ledmatrix_min_version": "2.0.0", + "notes": "Spotify: the decoded album cover is kept between polls. The previous art URL was read from the freshly replaced track dict, where it never exists, so every progress-only poll (every 2 s) dropped the cover and display() decoded it again. The enabled fallback is now true, matching the schema and the core." + }, { "version": "1.5.3", "released": "2026-10-02", @@ -205,7 +211,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/ledmatrix-stocks/README.md b/plugins/ledmatrix-stocks/README.md index 5b078b8b..971cf808 100644 --- a/plugins/ledmatrix-stocks/README.md +++ b/plugins/ledmatrix-stocks/README.md @@ -170,8 +170,9 @@ value it rejected. ### The inline chart -`display.toggle_chart` draws the day's price history behind the text, sized by -`chart_width_px` and `chart_height_px`: +`display.toggle_chart` draws the day's price history just right of each +symbol's text, sized by `chart_width_px` and `chart_height_px`. The chart is +part of the `scroll` ribbon; `switch` mode shows no chart: ![toggle_chart on and off](../../docs/assets/ledmatrix-stocks/toggle-chart.png) @@ -210,6 +211,7 @@ plugin: prices on one rotation slot, related headlines on another. **Chart isn't drawing** - Set `display.toggle_chart` to `true`. +- The chart is drawn only in `scroll` mode, not `switch`. - Charts need enough horizontal room next to each symbol. On a 64×32 panel they may be cropped — try a wider chain. diff --git a/plugins/ledmatrix-stocks/display_renderer.py b/plugins/ledmatrix-stocks/display_renderer.py index 6a16cb44..eff4828e 100644 --- a/plugins/ledmatrix-stocks/display_renderer.py +++ b/plugins/ledmatrix-stocks/display_renderer.py @@ -368,13 +368,9 @@ def create_static_display(self, symbol: str, data: Dict[str, Any]) -> Image.Imag is_crypto = data.get('is_crypto', False) - # Draw logo + # The logo is drawn once the text is measured (below), so the text + # can be placed beside it rather than on top of it. logo = self._get_stock_logo(symbol, is_crypto) - if logo: - # Ensure positions are integers - logo_x = 5 - logo_y = int((int(self.display_height) - logo.height) // 2) - image.paste(logo, (int(logo_x), int(logo_y)), logo) # Use custom fonts loaded from config symbol_font = self.symbol_font @@ -427,8 +423,26 @@ def create_static_display(self, symbol: str, data: Dict[str, Any]) -> Image.Imag else: change_bbox = (0, 0, 0, 0) - # Center everything - ensure integer - center_x = int(self.display_width) // 2 + # Center the text in the space right of the logo. Centering it on the + # whole panel drew it over the logo, which covers most of a 64-wide + # panel. The logo shrinks to the room the widest line leaves, and is + # left out (text centered on the panel) when that is under 8px. + logo_x = 5 + logo_gap = 4 + panel_width = int(self.display_width) + text_width = max(int(symbol_bbox[2] - symbol_bbox[0]), + int(price_bbox[2] - price_bbox[0]), + int(change_bbox[2] - change_bbox[0])) + center_x = panel_width // 2 + logo_room = panel_width - logo_x - logo_gap - text_width + if logo and logo_room >= 8: + if logo.width > logo_room: + logo = logo.copy() + logo.thumbnail((logo_room, logo_room)) + logo_y = int((int(self.display_height) - logo.height) // 2) + image.paste(logo, (int(logo_x), int(logo_y)), logo) + text_left = logo_x + logo.width + logo_gap + center_x = text_left + (panel_width - text_left) // 2 # Draw symbol symbol_width = int(symbol_bbox[2] - symbol_bbox[0]) diff --git a/plugins/ledmatrix-stocks/manifest.json b/plugins/ledmatrix-stocks/manifest.json index c75eadae..1a2d59e2 100644 --- a/plugins/ledmatrix-stocks/manifest.json +++ b/plugins/ledmatrix-stocks/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-stocks", "name": "Stock & Crypto Ticker", - "version": "2.11.2", + "version": "2.11.3", "description": "Displays stock tickers with prices, changes, and optional charts for stocks and cryptocurrencies. Supports scroll and switch display modes.", "author": "LEDMatrix Team", "homepage": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/ledmatrix-stocks", @@ -38,6 +38,12 @@ } ], "versions": [ + { + "version": "2.11.3", + "released": "2026-10-02", + "ledmatrix_min_version": "3.4.0", + "notes": "Switch mode no longer draws the price over the logo. The text was centered on the whole panel, so on a 64-wide panel it covered the logo and on 128 wide the change line ran into it. The text is now centered beside the logo, which shrinks to fit the widest line and is left out when there is no room for it. README: the inline chart is drawn in scroll mode only." + }, { "version": "2.11.2", "released": "2026-09-28", @@ -151,7 +157,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/ledmatrix-weather/CHANGELOG.md b/plugins/ledmatrix-weather/CHANGELOG.md index e051fbc5..0aebc04b 100644 --- a/plugins/ledmatrix-weather/CHANGELOG.md +++ b/plugins/ledmatrix-weather/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [2.8.1] - 2026-10-02 + +### Documentation +- README lists `radar_in_vegas` and the `customization` section (fonts, + colours, visibility and offsets for the current-conditions screen). + ## [2.8.0] - 2026-09-30 ### Added diff --git a/plugins/ledmatrix-weather/README.md b/plugins/ledmatrix-weather/README.md index ae6a1339..62f46030 100644 --- a/plugins/ledmatrix-weather/README.md +++ b/plugins/ledmatrix-weather/README.md @@ -91,8 +91,10 @@ 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 | +| `radar_in_vegas` | `true` | In the Vegas ticker, show the animated radar after the forecast cards (needs LEDMatrix core 3.8.0 and `show_radar` on). Off leaves the radar out of the ticker; the full-screen radar mode is unaffected | | `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 | +| `customization` | *(shipped styling)* | Font, size, colour, visibility and position offsets for the current-conditions screen's elements: `condition_text`, `temp_text`, `high_low_text`, `metric_text` (bottom bar) and `weather_icon`. Edited from the **Display Customization** section of the web UI. Needs a core with the element-style system; older cores ignore it | ## Display modes diff --git a/plugins/ledmatrix-weather/manifest.json b/plugins/ledmatrix-weather/manifest.json index b32621f6..3dd4250d 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.8.0", + "version": "2.8.1", "author": "ChuckBuilds", "class_name": "WeatherPlugin", "update_interval": 60, @@ -26,6 +26,12 @@ "radar" ], "versions": [ + { + "version": "2.8.1", + "released": "2026-10-02", + "ledmatrix_min_version": "3.8.0", + "notes": "Docs: the README now lists radar_in_vegas and the customization section (fonts, colours and offsets for the current-conditions screen). No code change." + }, { "version": "2.8.0", "released": "2026-09-30", @@ -246,7 +252,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-30", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/march-madness/README.md b/plugins/march-madness/README.md index ea1ca0d2..24296b8a 100644 --- a/plugins/march-madness/README.md +++ b/plugins/march-madness/README.md @@ -41,7 +41,6 @@ list. - **Round Logos**: Optional round-logo separators between game groups. - **Upset Highlighting**: Highlights upset winners (a higher seed beating a lower seed) in gold. -- **Bracket Progress**: Optionally shows which teams are still alive in each region. - **Favorite Teams**: Highlight your teams anywhere they appear in the bracket. - **Live Scores**: Live game scores with an automatically shortened refresh interval while games are in progress. @@ -95,7 +94,7 @@ truth. The keys below are the ones you'll typically set. | `leagues.ncaaw` | `true` | Show NCAA Women's Tournament games. | | `favorite_teams` | `[]` | Team abbreviations to highlight (e.g. `DUKE`, `UNC`). Empty shows all teams equally. | -### Display Options +### Display Options (`display_options`) | Key | Default | Range | Notes | |-----|---------|-------|-------| @@ -106,7 +105,7 @@ truth. The keys below are the ones you'll typically set. | `scroll_delay` | `0.02` | 0.001–0.1 | Seconds per step; see `scroll_speed`. | | `loop` | `true` | — | Loop the scroll continuously. | | `dynamic_duration` | `true` | — | Adjust the on-screen duration automatically based on content width. | -| `min_duration` | `30` | 10–300 | Minimum display duration in seconds (used with `dynamic_duration`). | +| `min_duration` | `30` | 10–300 | Minimum display duration in seconds. With `dynamic_duration` off, every slot lasts exactly this long. | | `max_duration` | `300` | 30–600 | Maximum display duration in seconds (used with `dynamic_duration`). | Three of these change what you see on the panel directly: @@ -117,7 +116,7 @@ Three of these change what you see on the panel directly: ![highlight_upsets on and off](../../docs/assets/march-madness/highlight-upsets.png) -### Data Settings +### Data Settings (`data_settings`) | Key | Default | Range | Notes | |-----|---------|-------|-------| diff --git a/plugins/march-madness/manifest.json b/plugins/march-madness/manifest.json index 707d4ab8..f735a16f 100644 --- a/plugins/march-madness/manifest.json +++ b/plugins/march-madness/manifest.json @@ -1,7 +1,7 @@ { "id": "march-madness", "name": "March Madness", - "version": "2.1.3", + "version": "2.1.4", "description": "NCAA March Madness tournament bracket tracker with round branding, seeded matchups, live scores, and upset highlighting", "author": "ChuckBuilds", "category": "sports", @@ -20,6 +20,12 @@ ">=3.4.0" ], "versions": [ + { + "version": "2.1.4", + "released": "2026-10-02", + "ledmatrix_min_version": "3.4.0", + "notes": "README only: removed a Bracket Progress feature the plugin does not have; min_duration is the whole slot when dynamic_duration is off; section headings name display_options and data_settings." + }, { "version": "2.1.3", "released": "2026-09-28", @@ -117,7 +123,7 @@ ], "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", "display_modes": [ diff --git a/plugins/masters-tournament/README.md b/plugins/masters-tournament/README.md index aa67a43c..0fa49b31 100644 --- a/plugins/masters-tournament/README.md +++ b/plugins/masters-tournament/README.md @@ -118,7 +118,7 @@ Enable/disable specific modes and configure their settings: |---|---|---| | `enabled` | `true` | Enable or disable the Masters Tournament plugin. | | `display_duration` | `20` | Duration in seconds to display each mode before rotating (5–300). | -| `update_interval` | `30` | How often to fetch new data in seconds (30s during tournament, 3600s off-season) (10–3600). | +| `update_interval` | `30` | How often, in seconds, the plugin checks for new data (10–3600). The ESPN responses it fetches are cached for 30s while a round is on, 5 min in the three days before the start, and 1 h otherwise, so most checks off-season are served from the cache. | | `player_card_duration` | `8` | Seconds each player card is shown before rotating to the next player in the player card display mode (1–300). | | `hole_display_duration` | `15` | Seconds between hole advances in course tour and hole-by-hole display modes (1–300). | | `page_display_duration` | `15` | Seconds between page advances in paginated modes (leaderboard, champions, tournament stats, schedule, course overview) (1–300). | @@ -359,7 +359,7 @@ The Masters is typically held: - **Tournament**: Thursday-Sunday (April 9-12) Plugin automatically detects tournament phase and adjusts: -- Update intervals (30s live, 5m practice, 1h off-season) +- Data cache lifetime (30s live, 5m in the three days before the start, 1h otherwise) - Cache duration - Mode prioritization @@ -376,7 +376,7 @@ cd ~/Github/ledmatrix-plugins/plugins/masters-tournament sudo systemctl restart ledmatrix # Monitor logs -tail -f /var/log/ledmatrix/ledmatrix.log +sudo journalctl -u ledmatrix -f ``` ### Adding New Display Modes @@ -391,9 +391,9 @@ tail -f /var/log/ledmatrix/ledmatrix.log ### ESPN Golf API Endpoints -- **Leaderboard**: `https://site.api.espn.com/apis/site/v2/sports/golf/pga/leaderboard` -- **Schedule**: `https://site.api.espn.com/apis/site/v2/sports/golf/pga/schedule` -- **News**: `https://site.api.espn.com/apis/site/v2/sports/golf/pga/news` +- **Leaderboard** (also the source of tee times and tournament dates): `https://site.api.espn.com/apis/site/v2/sports/golf/leaderboard` +- **Player bio**: `https://site.web.api.espn.com/apis/common/v3/sports/golf/pga/athletes/{player_id}` +- **Player overview**: `https://site.web.api.espn.com/apis/common/v3/sports/golf/pga/athletes/{player_id}/overview` No API key required. Rate limits apply (plugin respects with caching). diff --git a/plugins/masters-tournament/manifest.json b/plugins/masters-tournament/manifest.json index 86e7d9fa..bad4afb9 100644 --- a/plugins/masters-tournament/manifest.json +++ b/plugins/masters-tournament/manifest.json @@ -1,7 +1,7 @@ { "id": "masters-tournament", "name": "Masters Tournament", - "version": "3.2.4", + "version": "3.2.5", "description": "Broadcast-quality Masters Tournament display with real ESPN player headshots, accurate Augusta National hole layouts, fun facts, past champions, live leaderboards, and pixel-perfect LED matrix rendering", "category": "sports", "author": "ChuckBuilds", @@ -44,6 +44,12 @@ "height": 64 }, "versions": [ + { + "version": "3.2.5", + "released": "2026-10-02", + "ledmatrix_min_version": "3.4.0", + "notes": "README only: update_interval described as the check cadence (the 30s/5min/1h tiers are the data cache lifetime), ESPN endpoints corrected to the ones the plugin calls, log command is journalctl." + }, { "version": "3.2.4", "released": "2026-09-28", @@ -227,7 +233,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "compatible_versions": [ ">=3.4.0" ] diff --git a/plugins/mqtt-notifications/README.md b/plugins/mqtt-notifications/README.md index 99981aac..385f1cd7 100644 --- a/plugins/mqtt-notifications/README.md +++ b/plugins/mqtt-notifications/README.md @@ -90,7 +90,7 @@ centred, which suits short alerts and costs no CPU between redraws. | Key | Default | What it does | |---|---|---| -| `text.font_path` | `assets/fonts/PressStart2P-Regular.ttf` | Font file, TTF or BDF. Relative to the project root, or absolute. | +| `text.font_path` | `assets/fonts/PressStart2P-Regular.ttf` | Font file, TTF or BDF. Relative to the project root, or absolute. A BDF is a bitmap face and draws at the one size it declares, so `text.font_size` does not apply to it. | | `text.font_size` | `8` | Size in pixels. | | `text.text_color` | `[255, 255, 255]` | RGB triple. | | `text.background_color` | `[0, 0, 0]` | RGB triple. | @@ -289,11 +289,11 @@ To send a base64 encoded image, use the data URI format: ## Image Support -- **Formats**: PNG, JPEG, GIF (animated GIFs supported) +- **Formats**: PNG, JPEG, GIF (an animated GIF shows its first frame only) - **Base64**: Use data URI format: `data:image/png;base64,` - **File Paths**: Absolute paths or paths relative to LEDMatrix project root - **Resizing**: Images are automatically resized to fit the LED matrix while maintaining aspect ratio -- **Transparency**: RGBA images are converted to RGB with black background +- **Transparency**: RGBA images are flattened onto `text.background_color` (black by default) ## Troubleshooting diff --git a/plugins/mqtt-notifications/manager.py b/plugins/mqtt-notifications/manager.py index 9bb27e1b..81ddacc0 100644 --- a/plugins/mqtt-notifications/manager.py +++ b/plugins/mqtt-notifications/manager.py @@ -104,7 +104,8 @@ def __init__(self, plugin_id: str, config: Dict[str, Any], self.last_update_time = time.time() self.text_image_cache: Optional[Image.Image] = None self.image_cache: Optional[Image.Image] = None - + self.image_failed = False + # Load font self.font = self._load_font() @@ -164,15 +165,14 @@ def _load_font(self): self.logger.info("Loaded TTF font: %s", font_path) return font elif font_path.lower().endswith('.bdf'): - try: - import freetype - face = freetype.Face(font_path) - face.set_pixel_sizes(0, self.font_size) - self.logger.info("Loaded BDF font: %s", font_path) - return face - except ImportError: - self.logger.warning("freetype not available for BDF font, using default") - return ImageFont.load_default() + # PIL's FreeType loader opens a .bdf, but only at the one pixel + # size it declares. This used to return a raw freetype.Face, + # which PIL cannot draw with, so every message failed to render. + native = self._bdf_pixel_size(font_path) + size = native if native is not None else int(self.font_size) + font = ImageFont.truetype(font_path, size) + self.logger.info("Loaded BDF font: %s @ %s", font_path, size) + return font else: self.logger.warning("Unsupported font type: %s", font_path) return ImageFont.load_default() @@ -323,8 +323,9 @@ def _on_mqtt_message(self, client, userdata, msg): # pylint: disable=unused-arg # Clear caches when new message arrives self.text_image_cache = None self.image_cache = None + self.image_failed = False self.scroll_pos = 0.0 - + # Trigger on-demand display self._trigger_on_demand_display(message) @@ -660,13 +661,20 @@ def display(self, force_clear: bool = False) -> bool: if 'image' in content and content['image']: # Display image if self.image_cache is None: - img = self._load_image(content['image']) + # One attempt per message: a failed load used to be retried + # (and logged as an error) on every frame of the fast loop. + img = None + first_attempt = not getattr(self, 'image_failed', False) + if first_attempt: + img = self._load_image(content['image']) + self.image_failed = img is None if img: self.image_cache = self._resize_image(img) else: # Fallback to text if image loading fails if 'text' in content and content['text']: - self.logger.warning("Image loading failed, falling back to text") + if first_attempt: + self.logger.warning("Image loading failed, falling back to text") content = {'text': content['text']} else: return False diff --git a/plugins/mqtt-notifications/manifest.json b/plugins/mqtt-notifications/manifest.json index c58af295..4e923648 100644 --- a/plugins/mqtt-notifications/manifest.json +++ b/plugins/mqtt-notifications/manifest.json @@ -1,7 +1,7 @@ { "id": "mqtt-notifications", "name": "MQTT Notifications", - "version": "1.2.7", + "version": "1.2.8", "author": "ChuckBuilds", "description": "Display text or images from HomeAssistant via MQTT. Supports dynamic MQTT topics with wildcard support for flexible notification handling that interrupts the normal display rotation.", "category": "integration", @@ -18,6 +18,12 @@ "entry_point": "manager.py", "class_name": "MQTTNotificationsPlugin", "versions": [ + { + "released": "2026-10-02", + "version": "1.2.8", + "changelog": "A BDF font set in text.font_path now renders: it was loaded as a raw freetype face PIL cannot draw with, so every text message failed. It loads through PIL at the size the file declares, and the freetype-py dependency is dropped. An image that fails to load is tried once per message instead of on every frame of the fast loop, each attempt logging an error. Docs: an animated GIF shows its first frame, and transparency is flattened onto text.background_color.", + "ledmatrix_min_version": "2.0.0" + }, { "released": "2026-09-28", "version": "1.2.7", @@ -97,7 +103,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/mqtt-notifications/requirements.txt b/plugins/mqtt-notifications/requirements.txt index 9f4bbc9a..bb83cc7c 100644 --- a/plugins/mqtt-notifications/requirements.txt +++ b/plugins/mqtt-notifications/requirements.txt @@ -1,3 +1,2 @@ paho-mqtt>=1.6.0 Pillow>=12.2.0 -freetype-py>=2.4.0 diff --git a/plugins/news/manager.py b/plugins/news/manager.py index 8204852e..495ac761 100644 --- a/plugins/news/manager.py +++ b/plugins/news/manager.py @@ -1051,10 +1051,13 @@ def _fetch_feed_headlines(self, feed_name: str, feed_url: str) -> List[Dict]: link = item.find('link') if title is not None and title.text: + # An empty has text None; unescaping it + # raised, and the whole feed was dropped as failed. + desc_text = description.text if description is not None else None headline = { 'feed_name': feed_name, 'title': html.unescape(title.text).strip(), - 'description': html.unescape(description.text).strip() if description is not None else '', + 'description': html.unescape(desc_text).strip() if desc_text else '', 'published': pub_date.text if pub_date is not None else '', 'link': link.text if link is not None else '', 'timestamp': datetime.now().isoformat() diff --git a/plugins/news/manifest.json b/plugins/news/manifest.json index 60d41e25..e57cff20 100644 --- a/plugins/news/manifest.json +++ b/plugins/news/manifest.json @@ -1,7 +1,7 @@ { "id": "news", "name": "News Ticker", - "version": "1.6.4", + "version": "1.6.5", "description": "Displays scrolling news headlines from RSS feeds including sports news from ESPN, NCAA updates, and custom RSS sources", "author": "ChuckBuilds", "category": "content", @@ -20,6 +20,12 @@ "branch": "main", "plugin_path": "plugins/news", "versions": [ + { + "released": "2026-10-02", + "version": "1.6.5", + "changelog": "A feed whose items have an empty is no longer dropped. Unescaping the missing text raised, and the whole feed was treated as failed.", + "ledmatrix_min_version": "3.4.0" + }, { "released": "2026-09-28", "version": "1.6.4", @@ -154,7 +160,7 @@ ], "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", "display_modes": [ diff --git a/plugins/nfl-draft/README.md b/plugins/nfl-draft/README.md index 5793cdcc..b563e2c3 100644 --- a/plugins/nfl-draft/README.md +++ b/plugins/nfl-draft/README.md @@ -37,15 +37,15 @@ Plugin path: plugins/nfl-draft | Option | Type | Default | Description | |--------|------|---------|-------------| | `enabled` | boolean | `true` | Enable/disable the plugin | -| `display_duration` | number | `60` | Display duration in seconds | +| `display_duration` | number | `60` | Display duration in seconds (10–300); used when `dynamic_duration.enabled` is off | | `font` | string | `"PressStart2P-Regular.ttf"` | Font file from assets/fonts/ | | `player_name_font_size` | integer | `12` | Font size for player names | | `detail_font_size` | integer | `8` | Font size for pick number / position / college | | `player_name_color` | object | `{r:255,g:255,b:255}` | Player-name colour, as separate `player_name_color.r`, `player_name_color.g` and `player_name_color.b` values (0–255) | | `pick_number_color` | object | `{r:255,g:255,b:255}` | Detail-line colour, as separate `pick_number_color.r`, `pick_number_color.g` and `pick_number_color.b` values (0–255) | | `scroll_speed` | number | `30` | Scroll speed in pixels per second, snapped to the nearest speed the panel can move in whole pixels (30 runs at 33.3 on a 100 Hz panel). **Changed in 2.1.0:** this setting used to be ignored and every install scrolled at 100 px/s; set `100` for that speed | -| `live_refresh_interval` | integer | `600` | Refresh interval during live draft (seconds) | -| `projection_refresh_interval` | integer | `86400` | Refresh interval for projections (seconds) | +| `live_refresh_interval` | integer | `600` | Refresh interval during the live draft and the April 20–27 draft window (seconds, 60–1800) | +| `projection_refresh_interval` | integer | `86400` | Refresh interval for projections outside the draft window (seconds, 3600–172800) | | `draft_year` | integer | `0` | Draft year (0 = auto-detect current/upcoming) | | `show_position` | boolean | `true` | Show player position | | `show_college` | boolean | `true` | Show player college/school | @@ -68,7 +68,7 @@ Plugin path: plugins/nfl-draft | Key | Default | Notes | |---|---|---| -| `dynamic_duration.enabled` | `true` | . | +| `dynamic_duration.enabled` | `true` | Keep the scroll on screen until it has run through every pick (between `min_duration` and `max_duration`). Off, each slot lasts `display_duration`. | | `dynamic_duration.min_duration` | `30` | Minimum display duration in seconds. | | `dynamic_duration.max_duration` | `300` | Maximum display duration in seconds. | | `vegas_mode` | `"scroll"` | Override how this plugin appears in Vegas scroll mode. 'scroll' = individual picks scroll through the stream (default), 'fixed' = entire display scrolls by as one block, 'static' = scroll pauses while plugin displays for its duration — one of `scroll`, `fixed`, `static`. | diff --git a/plugins/nfl-draft/manifest.json b/plugins/nfl-draft/manifest.json index 95b18b37..b7715ab2 100644 --- a/plugins/nfl-draft/manifest.json +++ b/plugins/nfl-draft/manifest.json @@ -1,7 +1,7 @@ { "id": "nfl-draft", "name": "NFL Draft", - "version": "2.2.1", + "version": "2.2.2", "author": "ChuckBuilds", "description": "Displays projected NFL draft picks from ESPN with live draft tracking support during the annual NFL Draft event. Includes simulate_live mode to replay a completed draft using real ESPN core API data. Shows team logos, player names, positions, and pick numbers in a scrolling display.", "entry_point": "manager.py", @@ -24,6 +24,12 @@ ], "icon": "fas fa-football-ball", "versions": [ + { + "version": "2.2.2", + "released": "2026-10-02", + "ledmatrix_min_version": "3.4.0", + "notes": "README only: dynamic_duration.enabled is described (the row was empty), display_duration applies only with dynamic duration off, and the two refresh intervals give their ranges and when each applies." + }, { "version": "2.2.1", "released": "2026-09-28", @@ -172,5 +178,5 @@ "nfl_logos": "assets/sports/nfl_logos/" }, "license": "GPL-3.0", - "last_updated": "2026-09-28" + "last_updated": "2026-10-02" } diff --git a/plugins/nrl-scoreboard/CHANGELOG.md b/plugins/nrl-scoreboard/CHANGELOG.md index dd3b9672..bdab8c2f 100644 --- a/plugins/nrl-scoreboard/CHANGELOG.md +++ b/plugins/nrl-scoreboard/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [1.34.2] - 2026-10-02 + +### Documentation +- README: `scroll_settings.scroll_speed` is documented with its real default, + `50` px/s (it said `1.0`). +- README: the `game_limits` precedence note matches the code: a + `game_limits` value changed from its default wins, otherwise the root key + decides (it said `game_limits` wins whenever present). +- README: documents `odds_update_interval` (`3600` s), + `live_odds_update_interval` (`60` s) and the full-screen + `scroll_card.switch_show_date` / `switch_show_time` switches. No change in + behaviour. + ## [1.34.1] - 2026-10-01 ### Changed diff --git a/plugins/nrl-scoreboard/README.md b/plugins/nrl-scoreboard/README.md index c994afa3..bb9dc8bf 100644 --- a/plugins/nrl-scoreboard/README.md +++ b/plugins/nrl-scoreboard/README.md @@ -114,9 +114,10 @@ Which of three regimes you are in depends on `favorite_teams` and | `other_games_divisions` | `["fbs"]` | Which divisions non-favorite games may come from. Inert here — see below. **Hidden from the config form since 1.29.0 (still declared).** | All eight are declared **twice**: at the root of the config and inside -`game_limits`. Both render in the web UI and both are read. **`game_limits` wins -where the key is present**, and the root value is used otherwise. Set one place -or the other, not both. +`game_limits`. Both render in the web UI and both are read. The web UI saves a +value into both copies, so **a `game_limits` value you changed from its default +wins**, and the root value decides otherwise. Set one place or the other, not +both. Within the other-games pool the better matchup leads and each team appears once. The pool is each team's *next* game ordered by the best poll position of either @@ -302,6 +303,8 @@ full turn before the board moves on. | `recent_update_interval` | 60–86400 s | `3600` | **Advanced.** Refresh cadence for finished games. | | `upcoming_update_interval` | 60–86400 s | `3600` | **Advanced.** Refresh cadence for the schedule. | | `stale_game_timeout` | 60–3600 s | `300` | **Advanced.** Drop a live game the API has stopped updating. | +| `odds_update_interval` | 60–86400 s | `3600` | **Advanced.** How long fetched betting odds for a game that is not live are reused before asking again. Only matters with `show_odds` on. | +| `live_odds_update_interval` | 15–3600 s | `60` | **Advanced.** The same for a live game. | | `no_data_interval_seconds` | 5–86400 s | `300` | **Advanced.** Wait between live checks when there are no live games. Backs off further the longer nothing is found. | | `live_idle_max_interval_seconds` | 5–86400 s | `900` | **Advanced.** Ceiling for that back-off. | | `schedule_lookback_days` | 1–60 | `14` | **Advanced.** How far back to fetch for the Recent screen. | @@ -360,7 +363,7 @@ Nudge any element in pixels. All default to `0`, all live under | Key | Type | Default | |---|---|---| -| `scroll_settings.scroll_speed` | 0.01–200 px/s | `1.0` | +| `scroll_settings.scroll_speed` | 0.01–200 px/s | `50` | | `scroll_settings.scroll_delay` | 0.001–0.1 s | `0.01` (ignored; kept so saved configs still load -- scrolling is paced to the panel refresh). **Hidden from the config form since 1.29.0 (still declared).** | | `scroll_settings.gap_between_games` | 8–128 px | `24` | | `scroll_settings.show_league_separators` | boolean | `true` | @@ -391,7 +394,8 @@ full-screen scoreboard. | Date Format | `scroll_card.date_format` | `abbrev` | Scroll and Vegas cards: `Sep 19`, `9/19`, `19 Sep`, `19/9`, or `Fri Sep 19`. | | Full-Screen Date Format | `scroll_card.switch_date_format` | `numeric` | **Advanced.** The same for the full-screen scoreboard, plus `inherit`. It has its own default because the two displays disagree about what is normal: the cards have always written `Sep 19` and the full-screen scoreboard `9/19`, so a single shared default would restyle one of them. | | Time Format | `scroll_card.time_format` | `12h` | 12- or 24-hour clock. | -| Show Date / Show Time | `scroll_card.show_date`, `scroll_card.show_time` | `true` | Drop either line. | +| Show Date / Show Time | `scroll_card.show_date`, `scroll_card.show_time` | `true` | Drop either line from the scroll and Vegas cards. | +| Full-Screen Show Date / Show Time | `scroll_card.switch_show_date`, `scroll_card.switch_show_time` | `true` | The same for the full-screen upcoming scoreboard. | | Full-Screen Recent Date | `scroll_card.switch_recent_show_date` | `true` | Draw the date a finished game was played along the bottom of the full-screen recent scoreboard, written in the Full-Screen Date Format. | | Swap Date and Time | `scroll_card.swap_date_time` | `false` | Flip the two lines. Each display starts from its own order, so this flips rather than forces: scroll and Vegas cards put the time on top, the full-screen stack puts the date on top. | diff --git a/plugins/nrl-scoreboard/manifest.json b/plugins/nrl-scoreboard/manifest.json index 80529f0b..494178ba 100644 --- a/plugins/nrl-scoreboard/manifest.json +++ b/plugins/nrl-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "nrl-scoreboard", "name": "NRL Scoreboard", - "version": "1.34.1", + "version": "1.34.2", "author": "ChuckBuilds", "description": "Live, recent, and upcoming NRL (National Rugby League) games with real-time scores and game status.", "category": "sports", @@ -18,6 +18,12 @@ "nrl_upcoming" ], "versions": [ + { + "version": "1.34.2", + "released": "2026-10-02", + "ledmatrix_min_version": "3.8.0", + "notes": "README only: scroll_settings.scroll_speed is documented with its real default (50 px/s, not 1.0); the game_limits precedence note now matches the code (a game_limits value changed from its default wins, otherwise the root key); documents odds_update_interval / live_odds_update_interval and the full-screen scroll_card.switch_show_date / switch_show_time switches. No change in behaviour." + }, { "version": "1.34.1", "released": "2026-10-01", @@ -541,7 +547,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2026-09-30", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/odds-ticker/README.md b/plugins/odds-ticker/README.md index 9221119b..65340fe4 100644 --- a/plugins/odds-ticker/README.md +++ b/plugins/odds-ticker/README.md @@ -21,17 +21,17 @@ size from seeded games so it reproduces exactly. Team records come from a live per-team ESPN lookup that these offline renders skip, which is why they read `(N/A)`.* -A plugin for LEDMatrix that displays scrolling odds and betting lines for upcoming games across multiple sports leagues including NFL, NBA, MLB, NCAA Football, and NCAA Basketball. +A plugin for LEDMatrix that displays scrolling odds and betting lines for upcoming games across multiple sports leagues including NFL, NBA, MLB, NHL, MiLB, NCAA Football, NCAA Men's Basketball, and NCAA Baseball. ## Features -- **Multi-Sport Support**: NFL, NBA, MLB, NCAA Football, NCAA Basketball +- **Multi-Sport Support**: NFL, NBA, MLB, NHL, MiLB, NCAA Football, NCAA Men's Basketball, NCAA Baseball - **Scrolling Ticker Display**: Continuous scrolling of odds information - **Betting Lines**: Point spreads, money lines, and over/under totals - **Favorite Teams**: Prioritize odds for your favorite teams - **Broadcast Information**: Show channel logos and game times - **Configurable Display**: Adjustable scroll speed, duration, and filtering options -- **Background Data Fetching**: Efficient API calls without blocking display +- **Fetch Outside the Draw Loop**: Data is fetched and cached in `update()`, never while the ticker is drawing ## Configuration @@ -151,8 +151,11 @@ The plugin supports the following sports leagues: - **nfl**: NFL (National Football League) - **nba**: NBA (National Basketball Association) - **mlb**: MLB (Major League Baseball) +- **nhl**: NHL (National Hockey League) +- **milb**: Minor League Baseball - **ncaa_fb**: NCAA Football - **ncaam_basketball**: NCAA Men's Basketball +- **ncaa_baseball**: NCAA Baseball ## Team Abbreviations @@ -171,18 +174,19 @@ Common abbreviations: UGA, AUB, BAMA, CLEM, OSU, MICH, FSU, LSU, OU, TEX, etc. ### NCAA Basketball Teams Common abbreviations: DUKE, UNC, KANSAS, KENTUCKY, UCLA, ARIZONA, GONZAGA, BAYLOR, VILLANOVA, MICHIGAN, etc. -## Background Service +## Data Fetching -The plugin uses background data fetching for efficient API calls: +All network calls run in `update()`, never from the draw loop: -- Requests timeout after 30 seconds (configurable) -- Up to 3 retries for failed requests -- Priority level 2 (medium priority) -- Updates every hour by default (configurable) +- Requests time out after `data_settings.request_timeout` seconds (30 by default) +- Refreshes every `data_settings.update_interval` seconds (an hour by default), or every + `data_settings.live_game_update_interval` seconds while live games are on the ticker +- ESPN scoreboards are cached per league and day; odds per game ## Data Sources -Odds data is fetched from various sports data APIs and aggregated for display. The plugin integrates with the main LEDMatrix odds management system. +Games come from ESPN's public scoreboard API and betting lines from ESPN's public odds +API (through LEDMatrix's shared odds manager). No API key is required. ## Dependencies diff --git a/plugins/odds-ticker/manifest.json b/plugins/odds-ticker/manifest.json index d580d9bf..0c158ee1 100644 --- a/plugins/odds-ticker/manifest.json +++ b/plugins/odds-ticker/manifest.json @@ -1,7 +1,7 @@ { "id": "odds-ticker", "name": "Odds Ticker", - "version": "1.7.6", + "version": "1.7.7", "description": "Displays scrolling odds and betting lines for upcoming games across multiple sports leagues including NFL, NBA, MLB, NCAA Football, and more", "author": "ChuckBuilds", "category": "sports", @@ -20,6 +20,12 @@ "branch": "main", "plugin_path": "plugins/odds-ticker", "versions": [ + { + "version": "1.7.7", + "released": "2026-10-02", + "ledmatrix_min_version": "3.4.0", + "notes": "README only: lists all eight supported leagues (NHL, MiLB and NCAA Baseball were missing), describes data fetching as it is (in update(), request_timeout and the two refresh intervals) instead of a background service with retries and a priority it does not use, and names ESPN as the data source." + }, { "version": "1.7.6", "released": "2026-10-01", @@ -215,7 +221,7 @@ ], "stars": 0, "downloads": 0, - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "verified": true, "screenshot": "", "display_modes": [ diff --git a/plugins/of-the-day/README.md b/plugins/of-the-day/README.md index e84ac891..1a25a409 100644 --- a/plugins/of-the-day/README.md +++ b/plugins/of-the-day/README.md @@ -109,17 +109,17 @@ one](../../docs/assets/of-the-day/categories.png) | `of_the_day/word_of_the_day.json` | An English word a day, with definition and example | | `of_the_day/slovenian_word_of_the_day.json` | A Slovenian word a day, with its English meaning | -`category_order` controls the order categories are shown in. A category listed -in `category_order` but missing from `categories` is skipped, and one present -in `categories` but absent from `category_order` is appended after the -listed ones. +`category_order` controls the order categories are shown in. A name listed in +`category_order` that is not a category (no data file in `of_the_day/` and no +`categories` entry) is skipped, and a category absent from `category_order` is +appended after the listed ones. --- ## The Data File Format A data file is a JSON object keyed by **day of the year as a string**, `"1"` -through `"365"`: +through `"365"` (plus an optional `"366"` for 31 December of a leap year): ```json { @@ -148,7 +148,8 @@ day — and means a file needs 365 entries for full coverage. Missing days are skipped rather than erroring. Because it is keyed by day rather than date, the same file works every year. -For a leap year, day 366 has no entry unless you add one. +In a leap year 31 December is day 366; a file with no `"366"` entry shows its +day 365 entry that day, and one that has a `"366"` entry shows that instead. > Earlier documentation described these files as keyed by `YYYY-MM-DD`. They are > not — the bundled files and the loader both use the day-of-year number. A file @@ -160,8 +161,9 @@ For a leap year, day 366 has no entry unless you add one. - **Keep the subtitle short.** It is the line most often shown, and on a 128×32 panel roughly 24 characters survive before truncation. The description can be longer since it only needs to fit when it takes its turn. -- **Use the same fields throughout.** An entry missing `subtitle` or - `description` simply shows less; it does not fall back to another field. +- **Use the same fields throughout.** An entry missing `subtitle` shows the + title alone on that view; one missing `description` shows "No content" when + the description takes its turn. - **Cover all 365 days** if you want the category to appear every day. Gaps are skipped, so a sparse file means the category quietly disappears on the missing days. @@ -179,7 +181,7 @@ For a leap year, day 366 has no entry unless you add one. | `enabled` | boolean | `false` | Whether the plugin runs at all | | `categories` | object | *(empty)* | Your categories — see [above](#adding-a-category) | | `category_order` | array | `["word_of_the_day", "slovenian_word_of_the_day"]` | Display order | -| `display_duration` | number | `40` | Seconds each category holds the panel | +| `display_duration` | number | `40` | Seconds the plugin holds the panel per turn | | `display_rotate_interval` | number | `20` | Seconds before moving to the next category | | `subtitle_rotate_interval` | number | `10` | Seconds between the subtitle and description views | | `update_interval` | integer | `3600` | Seconds between checks for a new day | @@ -191,8 +193,8 @@ For a leap year, day 366 has no entry unless you add one. Three intervals stack, from slowest to fastest: -- **`display_duration`** (default `40`) — how long the whole category holds the - panel before the display controller moves on. +- **`display_duration`** (default `40`) — how long the plugin holds the panel + before the display controller moves on to the next plugin. - **`display_rotate_interval`** (default `20`) — how often the plugin moves on to the next enabled category. At the defaults, a 40-second turn with two categories shows each for 20 seconds. @@ -235,7 +237,11 @@ sizes](../../docs/assets/of-the-day/customization.png) | Element | `font` | `font_size` | `text_color` | |---------|--------|-------------|--------------| | `title_text` | `PressStart2P-Regular.ttf` | `8` (4–16) | `[255, 255, 255]` | -| `body_text` | `4x6-font.ttf` | `6` (4–12) | `[200, 200, 200]` | +| `body_text` | `4x6-font.ttf` | `6`\* (4–12) | `[200, 200, 200]` | + +\* A `font_size` equal to the schema default counts as untouched, and an +untouched body renders 4x6-font at 7px, its clean pixel grid. Any other value +is used as written. ```json { @@ -253,7 +259,19 @@ sizes](../../docs/assets/of-the-day/customization.png) > `color` in your config is silently ignored, with no warning and no visible > change. It is an easy mistake to make from reading the schema. -Both elements also accept `x_offset` and `y_offset` for nudging position. +Both elements also take `x_offset` and `y_offset` for nudging position. These +live in a separate `layout` block, not beside `font_size`: + +```json +{ + "customization": { + "layout": { + "title_text": { "x_offset": 0, "y_offset": 2 }, + "body_text": { "x_offset": 0, "y_offset": -1 } + } + } +} +``` Customization needs a LEDMatrix core with the element-style system. On an older core the section is not offered and the classic styling above is used, which is @@ -342,9 +360,16 @@ web UI rather than run directly. ### Tests +Run from the repo root with a LEDMatrix core checkout on the import path +(`PYTHONPATH=/path/to/LEDMatrix`); without it the tests cannot import +`src.plugin_system`: + ```bash python plugins/of-the-day/test_text_fitting.py python plugins/of-the-day/test_element_styles.py +python plugins/of-the-day/test_category_resolution.py +python plugins/of-the-day/test_leap_day_and_rollover.py +python plugins/of-the-day/test_timezone_and_config_change.py ``` ### Regenerating the images in this README diff --git a/plugins/of-the-day/config_schema.json b/plugins/of-the-day/config_schema.json index 28a67643..1d349f0b 100644 --- a/plugins/of-the-day/config_schema.json +++ b/plugins/of-the-day/config_schema.json @@ -89,7 +89,7 @@ "type": "number", "default": 40, "minimum": 10, - "description": "How long to display each category in seconds" + "description": "Seconds the plugin holds the panel per turn; categories rotate within it every display_rotate_interval" }, "file_manager": { "type": "null", @@ -106,7 +106,7 @@ "create": "create-file", "toggle": "toggle-category" }, - "upload_hint": "JSON files with day numbers 1\u2013365 as keys", + "upload_hint": "JSON files with day numbers 1\u2013366 as keys", "directory_label": "of_the_day/", "create_fields": [ { diff --git a/plugins/of-the-day/manager.py b/plugins/of-the-day/manager.py index a8b9a1ae..24e8c598 100644 --- a/plugins/of-the-day/manager.py +++ b/plugins/of-the-day/manager.py @@ -379,7 +379,12 @@ def display(self, force_clear: bool = False) -> None: self.last_rotation_time = current_time category_changed = True self.display_needs_update = True - + + # The list can shrink under the index (a new day where a sparse + # file has no entry); wrap instead of raising IndexError, which + # showed "Error" until the next category rotation. + self.current_category_index %= len(enabled_categories) + # Get current category category_name = enabled_categories[self.current_category_index] category_config = self.categories.get(category_name, {}) diff --git a/plugins/of-the-day/manifest.json b/plugins/of-the-day/manifest.json index 5e2fc3c2..09cd11dc 100644 --- a/plugins/of-the-day/manifest.json +++ b/plugins/of-the-day/manifest.json @@ -1,7 +1,7 @@ { "id": "of-the-day", "name": "Of The Day Display", - "version": "1.4.8", + "version": "1.4.9", "author": "ChuckBuilds", "description": "Display daily featured content like Word of the Day, Bible verses, or custom daily items. Supports multiple categories with rotating display and configurable data sources.", "category": "information", @@ -21,6 +21,12 @@ "of_the_day" ], "versions": [ + { + "released": "2026-10-02", + "version": "1.4.9", + "changelog": "When a new day leaves fewer categories with an entry than before (a sparse data file with no entry for today), the display wraps its category index instead of raising IndexError and showing 'Error' until the next category rotation. The file manager's upload action now rejects filenames containing '..', '/' or a backslash, as get/save/delete already did, and upload/save accept a day-366 key, which the loader already uses on 31 December of a leap year. README: display_duration is the plugin's whole turn (not per category), day 366 falls back to day 365, a missing description shows 'No content', offsets live under customization.layout, an untouched body font_size renders at 7px, and the test list is complete.", + "ledmatrix_min_version": "2.0.0" + }, { "released": "2026-09-28", "version": "1.4.8", @@ -109,7 +115,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/of-the-day/scripts/save_file.py b/plugins/of-the-day/scripts/save_file.py index 7fc335f3..9dad4c7b 100755 --- a/plugins/of-the-day/scripts/save_file.py +++ b/plugins/of-the-day/scripts/save_file.py @@ -46,7 +46,7 @@ if not isinstance(content, dict): print(json.dumps({ 'status': 'error', - 'message': 'JSON must be an object with day numbers (1-365) as keys' + 'message': 'JSON must be an object with day numbers (1-366) as keys' })) sys.exit(1) @@ -54,16 +54,16 @@ for key in content.keys(): try: day_num = int(key) - if day_num < 1 or day_num > 365: + if day_num < 1 or day_num > 366: print(json.dumps({ 'status': 'error', - 'message': f'Day number {day_num} is out of range (must be 1-365)' + 'message': f'Day number {day_num} is out of range (must be 1-366)' })) sys.exit(1) except ValueError: print(json.dumps({ 'status': 'error', - 'message': f'Invalid key "{key}": must be a day number (1-365)' + 'message': f'Invalid key "{key}": must be a day number (1-366)' })) sys.exit(1) diff --git a/plugins/of-the-day/scripts/upload_file.py b/plugins/of-the-day/scripts/upload_file.py index 769df727..135d8fb1 100755 --- a/plugins/of-the-day/scripts/upload_file.py +++ b/plugins/of-the-day/scripts/upload_file.py @@ -26,6 +26,15 @@ })) sys.exit(1) + # Security: ensure filename doesn't contain path traversal (same check + # as get/save/delete) + if '..' in filename or '/' in filename or '\\' in filename: + print(json.dumps({ + 'status': 'error', + 'message': 'Invalid filename' + })) + sys.exit(1) + # Validate filename if not filename.endswith('.json'): print(json.dumps({ @@ -48,7 +57,7 @@ if not isinstance(data, dict): print(json.dumps({ 'status': 'error', - 'message': 'JSON must be an object with day numbers (1-365) as keys' + 'message': 'JSON must be an object with day numbers (1-366) as keys' })) sys.exit(1) @@ -56,16 +65,16 @@ for key in data.keys(): try: day_num = int(key) - if day_num < 1 or day_num > 365: + if day_num < 1 or day_num > 366: print(json.dumps({ 'status': 'error', - 'message': f'Day number {day_num} is out of range (must be 1-365)' + 'message': f'Day number {day_num} is out of range (must be 1-366)' })) sys.exit(1) except ValueError: print(json.dumps({ 'status': 'error', - 'message': f'Invalid key "{key}": must be a day number (1-365)' + 'message': f'Invalid key "{key}": must be a day number (1-366)' })) sys.exit(1) diff --git a/plugins/of-the-day/test_leap_day_and_rollover.py b/plugins/of-the-day/test_leap_day_and_rollover.py index eb091ce2..c1bd9a63 100644 --- a/plugins/of-the-day/test_leap_day_and_rollover.py +++ b/plugins/of-the-day/test_leap_day_and_rollover.py @@ -62,7 +62,30 @@ def plugin(today, data): check("the first update after midnight loads the new day", p.current_day == date(2026, 9, 28) and p.current_items, p.current_day) +# 3. A new day can leave fewer categories with an entry (a sparse file has +# none for today). The category index pointed past the shorter list, so +# display() raised IndexError and showed "Error" until the next rotation. +p = object.__new__(OfTheDayPlugin) +p.logger = logging.getLogger("test-of-the-day") +p.categories = {"a": {"enabled": True}, "b": {"enabled": True}} +p.category_order = ["a", "b"] +p.current_items = {"a": {"title": "only"}} # "b" has no entry today +p.current_category_index = 1 # was showing "b" yesterday +p.rotation_state = 0 +now = time.time() +p.last_category_rotation_time = p.last_rotation_time = now +p.display_rotate_interval = p.subtitle_rotate_interval = 3600 +p.display_needs_update = True +p.last_displayed_category = p.last_displayed_rotation_state = None +drawn = [] +p._display_title = lambda cfg, item: drawn.append(("title", item)) +p._display_content = lambda cfg, item: drawn.append(("content", item)) +p._display_error = lambda: drawn.append(("error", None)) +p.display() +check("a shrunken category list wraps the index instead of showing Error", + drawn == [("title", {"title": "only"})], drawn) + print() -failed = [c for c, ok in results if not ok] +failed =[c for c, ok in results if not ok] print(f"{len(results) - len(failed)}/{len(results)} passed") sys.exit(1 if failed else 0) diff --git a/plugins/olympics/README.md b/plugins/olympics/README.md index e75922f0..3bb7ce3c 100644 --- a/plugins/olympics/README.md +++ b/plugins/olympics/README.md @@ -36,7 +36,7 @@ Screenshot Preview: - During Olympics: Countdown to closing ceremony - **Olympics Logo**: Displays Olympics logo (image or programmatically drawn Olympic rings) - **Adaptive Text Display**: Automatically adjusts text size and layout for different display sizes -- **Multiple Olympics Support**: Includes dates for upcoming Olympics through 2032 +- **Multiple Olympics Support**: Includes dates for the Games through Los Angeles 2028 ## Installation @@ -71,9 +71,8 @@ under `olympics`. The schema sets `additionalProperties: false`, so a key that is not listed below fails schema validation: the core logs a warning and flags the plugin as degraded in the web UI, but still loads it with that key ignored. `notifications_enabled`, `favorite_countries` and `webhooks` are still declared -for that reason: they are kept for compatibility and ignored, and are still -shown in the settings form (titled "deprecated") until core honours -`x-display: hidden`. The full schema is +for that reason: they are kept for compatibility and ignored, and are hidden +from the settings form (`x-display: hidden`). The full schema is [`config_schema.json`](config_schema.json). ### Basics @@ -174,7 +173,6 @@ The plugin will look for an Olympics logo image in the following locations: - `olympics-icon.png` - `logo.png` - `assets/olympics-logo.png` -- `assets/logo.png` If no image is found, the plugin will automatically draw the Olympic rings programmatically as a fallback. @@ -183,11 +181,11 @@ If no image is found, the plugin will automatically draw the Olympic rings progr ## Dependencies - Python 3.7+ -- PIL/Pillow (for image handling) +- PIL/Pillow and pytz (provided by LEDMatrix) +- `requests` and `lxml` (listed in `requirements.txt`; the plugin store installs + them) for the medal, schedule and results pages fetched during the Games - LEDMatrix 2.0.0 or higher -No additional Python packages are required beyond what LEDMatrix provides. - ## Troubleshooting ### Logo Image Not Displaying @@ -199,8 +197,8 @@ If the logo image doesn't appear: ### Countdown Not Updating -- The countdown updates based on `update_interval` (default: 1 hour) -- The countdown changes once per day, so hourly updates are sufficient +- Data refreshes every `update_interval` seconds (default: 300, five minutes) +- The day count itself is recomputed each time the screen is drawn - Check the plugin logs for any errors ### Text Not Fitting @@ -222,8 +220,11 @@ olympics/ ├── config_schema.json # Configuration schema ├── README.md # This file ├── requirements.txt # Python dependencies -└── assets/ # Optional: Olympics logo image - └── olympics-logo.png +├── olympics-logo.png # Logo drawn beside the countdown +├── data/ # Games table, olympics.com scraping, caching +├── renderers/ # Countdown, medal, event and alert cards +├── scripts/ # Asset/schedule generators (not loaded at runtime) +└── assets/ # Flags, sport icons, Olympic rings ``` ### Testing @@ -243,5 +244,5 @@ ChuckBuilds ## Version -1.0.0 +See `version` and the `versions` history in [`manifest.json`](manifest.json). diff --git a/plugins/olympics/manifest.json b/plugins/olympics/manifest.json index a8339233..c3822416 100644 --- a/plugins/olympics/manifest.json +++ b/plugins/olympics/manifest.json @@ -1,7 +1,7 @@ { "id": "olympics", "name": "Olympics", - "version": "3.1.4", + "version": "3.1.5", "author": "ChuckBuilds", "description": "Enhanced Olympics plugin with live medal counts, upcoming events, results, and countdown. Supports Vegas scroll mode and regular display mode.", "category": "sports", @@ -37,6 +37,12 @@ "country_tracking": true }, "versions": [ + { + "version": "3.1.5", + "released": "2026-10-02", + "ledmatrix_min_version": "2.0.0", + "notes": "README only: update_interval default is 300 seconds (said one hour), requests and lxml are required (said none), the logo search list, Games table, project layout and version reference match the plugin, and the deprecated keys are hidden from the settings form." + }, { "version": "3.1.4", "released": "2026-10-02", diff --git a/plugins/on-air/README.md b/plugins/on-air/README.md index ccb64d68..2469bde8 100644 --- a/plugins/on-air/README.md +++ b/plugins/on-air/README.md @@ -67,9 +67,9 @@ directly. The full schema is [`config_schema.json`](config_schema.json). | **Text Color** | `text_color` | `[255, 255, 255]` | RGB color of the sign text. | | **Background Color** | `background_color` | `[200, 10, 10]` | RGB background color — broadcast red by default. | | **Font** | `font_path` | *(blank)* | Path to a TTF font relative to the LEDMatrix root (e.g. `assets/fonts/PressStart2P-Regular.ttf`). Blank auto-selects one from the LEDMatrix assets folder, sized to 80% of display height. | -| **Font Size (px)** | `font_size` | `0` | Font height in pixels when a custom Font is set. `0` auto-sizes to 80% of display height. | +| **Font Size (px)** | `font_size` | `0` | Font height in pixels when a custom Font is set (`0` uses 8 px). Without a Font, the auto-selected font is sized to 80% of display height and this setting is ignored. | | **MQTT Broker Host** | `mqtt_host` | `localhost` | IP or hostname of your MQTT broker. | -| **MQTT Port** | `mqtt_port` | `1883` | Broker port (use 8883 for TLS). | +| **MQTT Port** | `mqtt_port` | `1883` | Broker port. TLS is not supported, so use a plain (non-TLS) listener. | | **MQTT Username** | `mqtt_username` | *(blank)* | Leave blank if no auth required. | | **MQTT Password** | `mqtt_password` | *(blank)* | Leave blank if no auth required. Marked secret, so the web UI masks it. | | **Command Topic** | `command_topic` | `ledmatrix/on-air/set` | Topic the plugin **subscribes** to (publish `ON`/`OFF` or JSON). | @@ -489,7 +489,7 @@ mosquitto_pub -h -t ledmatrix/on-air/set -m OFF ### The HA switch shows "Unavailable" - Confirm you reloaded your config after adding the `mqtt: switch:` block. -- Check the state topic: the switch won't show a valid state until the plugin has published at least once. Toggle it once from the command line to prime the state. +- Check the state topic: the plugin publishes its current state (and availability) every time it connects to the broker, so the switch should show `OFF` shortly after LEDMatrix starts. If it does not, the plugin is not reaching the broker. ### HA switch state doesn't update after sending OFF @@ -506,5 +506,5 @@ mosquitto_pub -h -t ledmatrix/on-air/set -m OFF - Set `retain: false` in your HA switch config. - Clear any retained messages on the command topic: ```bash - mosquitto_pub -h -t ledmatrix/on-air/set -m "" -r -n + mosquitto_pub -h -t ledmatrix/on-air/set -r -n ``` diff --git a/plugins/on-air/config_schema.json b/plugins/on-air/config_schema.json index 5d4f04cc..0050f136 100644 --- a/plugins/on-air/config_schema.json +++ b/plugins/on-air/config_schema.json @@ -82,7 +82,7 @@ "minimum": 1, "maximum": 65535, "title": "Broker Port", - "description": "Default is 1883. Use 8883 for TLS." + "description": "Default is 1883. TLS is not supported, so point this at a plain (non-TLS) listener." }, "mqtt_username": { "type": "string", diff --git a/plugins/on-air/manager.py b/plugins/on-air/manager.py index 3666215d..be7ae1e7 100644 --- a/plugins/on-air/manager.py +++ b/plugins/on-air/manager.py @@ -36,7 +36,18 @@ from src.plugin_system.base_plugin import BasePlugin -_PLUGIN_VERSION = "1.2.0" +def _manifest_version() -> str: + """manifest.json's version, so HA's sw_version tracks the release (a + hard-coded constant here stayed at 1.2.0 through every later bump).""" + try: + with open(Path(__file__).resolve().parent / 'manifest.json', + encoding='utf-8') as f: + return str(json.load(f).get('version', 'unknown')) + except (OSError, ValueError): + return 'unknown' + + +_PLUGIN_VERSION = _manifest_version() def _rgb(value, default) -> Tuple[int, int, int]: @@ -555,6 +566,13 @@ def _on_mqtt_connect(self, client, userdata, flags, rc): # pylint: disable=unus client.subscribe(self.command_topic, qos=1) self._publish_availability(True) self._publish_discovery() + # Re-announce the current state. Both topics are retained, so + # after a restart (the sign always comes up dark) Home Assistant + # kept showing the last ON until the next toggle. + with self.state_lock: + on_air, label = self.on_air, self.label + self._publish_state(on_air) + self._publish_label(label if on_air else '') self.logger.info("MQTT connected — subscribed to %s", self.command_topic) else: self.mqtt_connecting = False diff --git a/plugins/on-air/manifest.json b/plugins/on-air/manifest.json index 163a839a..06e7bb3f 100644 --- a/plugins/on-air/manifest.json +++ b/plugins/on-air/manifest.json @@ -1,7 +1,7 @@ { "id": "on-air", "name": "On Air Light", - "version": "1.2.13", + "version": "1.2.14", "author": "ChuckBuilds", "description": "Retro broadcast ON AIR tally light. Activate remotely via MQTT or Home Assistant to signal you're on a call, recording, or live \u2014 stays on until you turn it off.", "entry_point": "manager.py", @@ -22,6 +22,12 @@ ">=2.0.0" ], "versions": [ + { + "version": "1.2.14", + "released": "2026-10-02", + "ledmatrix_min_version": "2.0.0", + "notes": "Home Assistant no longer shows the sign as on after LEDMatrix restarts: the state and label topics are retained, and the plugin now republishes its current state each time it connects instead of only on the next toggle. The device's sw_version comes from manifest.json (it was stuck at 1.2.0). Docs: TLS is not supported (the port hint said to use 8883), font_size 0 means 8 px with a custom font, and the retained-message clearing command no longer passes both -m and -n." + }, { "version": "1.2.13", "released": "2026-09-29", @@ -123,7 +129,7 @@ "license": "GPL-3.0", "homepage": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/on-air", "config_schema": "config_schema.json", - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/pomodoro-timer/README.md b/plugins/pomodoro-timer/README.md index 5566e3f2..f8e61b0b 100644 --- a/plugins/pomodoro-timer/README.md +++ b/plugins/pomodoro-timer/README.md @@ -178,7 +178,7 @@ edit the file directly or read it back in a log line. The full schema is |---|---|---|---| | **Enable MQTT Control** | `mqtt_enabled` | `true` | Connect to a broker so the timer can be driven remotely. | | **Broker Address** | `mqtt_host` | `localhost` | IP or hostname of your MQTT broker. | -| **Broker Port** | `mqtt_port` | `1883` | Use 8883 for TLS. | +| **Broker Port** | `mqtt_port` | `1883` | The plugin connects without TLS, so point it at a plain-MQTT listener (usually 1883); a TLS-only port such as 8883 will not connect. | | **Username / Password** | `mqtt_username`
`mqtt_password` | *(blank)* | Leave blank for an anonymous broker. The password is marked secret, so the web UI masks it. | | **Command Topic** | `command_topic` | `ledmatrix/pomodoro/set` | Everything the plugin publishes is derived from this topic's base. | | **State Topic** | `state_topic` | `ledmatrix/pomodoro/state` | `ON` while a session is active, `OFF` when idle. | @@ -206,7 +206,7 @@ edit the file directly or read it back in a log line. The full schema is | **Pulse the Current Session Dot** | `pulse_active_pip` | `true` | Slowly blink the pip for the session you're in, so the row reads as "two done, on the third" rather than just a count. | | **Work / Short Break / Long Break / Idle / Paused Label** | `work_label`
`short_break_label`
`long_break_label`
`idle_label`
`paused_label` | `FOCUS` / `BREAK` / `LONG BREAK` / `POMODORO` / `PAUSED` | The on-screen text for each state, up to 24 characters. Blank the Paused Label to keep showing the phase name while paused. | | **Font** | `font_path` | *(blank)* | Path to a TTF relative to the LEDMatrix root, e.g. `assets/fonts/PressStart2P-Regular.ttf`. Blank uses the display's default font. | -| **Font Size (px)** | `font_size` | `0` | Fix the countdown height in pixels. `0` sizes it automatically to the panel. | +| **Font Size (px)** | `font_size` | `0` | Fix the font height in pixels for the phase label and, with the `pixel` digit style, the countdown. `0` sizes them automatically to the panel. The `seven_segment` countdown ignores it, and a label taller than its row is not drawn. | ### Behavior diff --git a/plugins/pomodoro-timer/config_schema.json b/plugins/pomodoro-timer/config_schema.json index 93209875..910bda77 100644 --- a/plugins/pomodoro-timer/config_schema.json +++ b/plugins/pomodoro-timer/config_schema.json @@ -126,7 +126,7 @@ "minimum": 1, "maximum": 65535, "title": "Broker Port", - "description": "Default is 1883. Use 8883 for TLS." + "description": "Default is 1883. The connection is plain MQTT: TLS is not supported, so a TLS-only port such as 8883 will not connect." }, "mqtt_username": { "type": "string", diff --git a/plugins/pomodoro-timer/manager.py b/plugins/pomodoro-timer/manager.py index b6465bf0..da3cc0c1 100644 --- a/plugins/pomodoro-timer/manager.py +++ b/plugins/pomodoro-timer/manager.py @@ -48,7 +48,19 @@ from src.plugin_system.base_plugin import BasePlugin -_PLUGIN_VERSION = "1.0.0" + +def _manifest_version() -> str: + """manifest.json's version, so HA's sw_version tracks the release (a + hard-coded constant here stayed at 1.0.0 through every later bump).""" + try: + with open(Path(__file__).resolve().parent / "manifest.json", + encoding="utf-8") as f: + return str(json.load(f).get("version", "unknown")) + except (OSError, ValueError): + return "unknown" + + +_PLUGIN_VERSION = _manifest_version() PHASE_IDLE = "idle" PHASE_WORK = "work" @@ -447,6 +459,11 @@ def _apply_command(self, command: str, payload: Dict[str, Any]) -> None: duration = max(1.0, float(payload["duration_seconds"])) except (TypeError, ValueError): duration = None + # JSON accepts 1e999 (and float() accepts "inf"); an infinite + # deadline made every snapshot raise OverflowError, so the panel + # showed the error screen until a RESET. + if duration is not None and not math.isfinite(duration): + duration = None if command in ("START", "ON", "PLAY"): started = True @@ -568,7 +585,7 @@ def _set_number_locked(self, field: str, raw: Any) -> bool: _, _, low, high, _, _ = spec try: value = int(round(float(raw))) - except (TypeError, ValueError): + except (TypeError, ValueError, OverflowError): self.logger.warning("Invalid value for %s: %r", field, raw) return False value = max(low, min(high, value)) diff --git a/plugins/pomodoro-timer/manifest.json b/plugins/pomodoro-timer/manifest.json index 9b0ebb02..9eac2f44 100644 --- a/plugins/pomodoro-timer/manifest.json +++ b/plugins/pomodoro-timer/manifest.json @@ -1,7 +1,7 @@ { "id": "pomodoro-timer", "name": "Pomodoro Timer", - "version": "1.3.9", + "version": "1.3.10", "author": "ChuckBuilds", "description": "A configurable Pomodoro focus/break timer for your matrix. Set the work and break lengths, then start, pause, skip, or reset it over MQTT \u2014 with Home Assistant auto-discovery so the whole timer shows up as a device with no YAML.", "entry_point": "manager.py", @@ -22,6 +22,12 @@ ">=2.0.0" ], "versions": [ + { + "version": "1.3.10", + "released": "2026-10-02", + "ledmatrix_min_version": "2.0.0", + "notes": "A JSON command with an infinite duration (1e999, or \"inf\" as a string) is ignored instead of setting an infinite deadline, which made every frame and every state publish raise OverflowError until a RESET. The Home Assistant device's sw_version is read from manifest.json; it was hard-coded at 1.0.0. Docs: the broker connection has no TLS, so the 'Use 8883 for TLS' hint is gone from the settings form and README, and the README's font_size row now says it sizes the label and pixel-style countdown, not the seven-segment one." + }, { "version": "1.3.9", "released": "2026-09-29", @@ -118,7 +124,7 @@ "license": "GPL-3.0", "homepage": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/pomodoro-timer", "config_schema": "config_schema.json", - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/pomodoro-timer/test_pomodoro_timer.py b/plugins/pomodoro-timer/test_pomodoro_timer.py index 66e8436c..c01c4ff5 100644 --- a/plugins/pomodoro-timer/test_pomodoro_timer.py +++ b/plugins/pomodoro-timer/test_pomodoro_timer.py @@ -256,6 +256,24 @@ def test_json_command_with_custom_duration_and_label(): assert p.work_minutes == 25 +def test_an_infinite_duration_is_ignored_not_fatal(): + # 1e999 is valid JSON for infinity; it used to set an infinite deadline + # that made every snapshot (and so every frame) raise OverflowError. + for raw in (b'{"command": "start", "duration_minutes": 1e999}', + b'{"command": "start", "duration_seconds": "inf"}'): + p = make_plugin() + p._apply_command(*p._parse_command(raw)) + snap = p._snapshot() + assert snap["phase"] == "work" + assert snap["remaining"] == "25:00" + assert p.display() is True + + +def test_sw_version_tracks_the_manifest(): + manifest = json.loads((PLUGIN_DIR / "manifest.json").read_text(encoding="utf-8")) + assert MODULE._PLUGIN_VERSION == manifest["version"] + + def test_settings_only_payload_updates_durations(): p = make_plugin() command, payload = p._parse_command(b'{"work_minutes": 45, "short_break_minutes": 10}') diff --git a/plugins/soccer-scoreboard/CHANGELOG.md b/plugins/soccer-scoreboard/CHANGELOG.md index 6d2853d9..4645dc21 100644 --- a/plugins/soccer-scoreboard/CHANGELOG.md +++ b/plugins/soccer-scoreboard/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [2.39.2] - 2026-10-02 + +### Fixed +- A custom league whose `game_limits` were never opened in the web UI + (the row editor sends them back blank) showed 5 recent games, not the `1` + its settings page displays: `_adapt_config_for_custom_league` now falls + back to the schema default. +- `custom_leagues[].filtering.show_all_live` defaults to `false` in the + schema, matching the built-in leagues and what the plugin already did when + the value was absent; the settings page showed it on while it was off. + +### Documentation +- README: documents `.odds_update_interval` (`3600` s), + `.live_odds_update_interval` (`60` s) and the full-screen + `scroll_card.switch_show_date` / `switch_show_time` switches. + ## [2.39.1] - 2026-10-01 ### Changed diff --git a/plugins/soccer-scoreboard/README.md b/plugins/soccer-scoreboard/README.md index d5dedfe9..a92e38bd 100644 --- a/plugins/soccer-scoreboard/README.md +++ b/plugins/soccer-scoreboard/README.md @@ -457,6 +457,8 @@ See [The selection settings](#the-selection-settings). | `.recent_update_interval` | number | `3600` | How often the finished-matches list is rebuilt. This also sets how soon a match that has just ended can appear. | | `.upcoming_update_interval` | number | `3600` | How often the upcoming-matches list is rebuilt. Selection and the non-favorite rotation both run on the display side, so this governs only the fetch. | | `.stale_game_timeout` | number | `300` | Drop a live match the API has stopped updating. | +| `.odds_update_interval` | 60–86400 s | `3600` | How often betting odds for recent and upcoming matches are refreshed. Odds are cached per match, so this bounds requests rather than adding them. Only matters with `show_odds` on. | +| `.live_odds_update_interval` | 15–3600 s | `60` | How often betting odds for matches in progress are refreshed. | ### Celebrations @@ -546,7 +548,8 @@ Plugin-wide, not per league. | Date Format | `scroll_card.date_format` | `abbrev` | Scroll and Vegas cards: `Sep 19`, `9/19`, `19 Sep`, `19/9`, or `Fri Sep 19`. | | Full-Screen Date Format | `scroll_card.switch_date_format` | `numeric` | **Advanced.** The same for the full-screen scoreboard, plus `inherit`. It has its own default because the two displays disagree about what is normal. | | Time Format | `scroll_card.time_format` | `12h` | 12- or 24-hour clock. | -| Show Date / Show Time | `scroll_card.show_date`, `scroll_card.show_time` | `true` | Drop either line. | +| Show Date / Show Time | `scroll_card.show_date`, `scroll_card.show_time` | `true` | Drop either line from the scroll and Vegas cards. | +| Full-Screen Show Date / Show Time | `scroll_card.switch_show_date`, `scroll_card.switch_show_time` | `true` | The same for the full-screen upcoming scoreboard. | | Full-Screen Recent Date | `scroll_card.switch_recent_show_date` | `true` | Draw the date a finished game was played along the bottom of the full-screen recent scoreboard, written in the Full-Screen Date Format. | | Swap Date and Time | `scroll_card.swap_date_time` | `false` | Flip the two lines. Each display starts from its own order, so this flips rather than forces. | diff --git a/plugins/soccer-scoreboard/config_schema.json b/plugins/soccer-scoreboard/config_schema.json index dc9d8731..1c896461 100644 --- a/plugins/soccer-scoreboard/config_schema.json +++ b/plugins/soccer-scoreboard/config_schema.json @@ -5530,7 +5530,7 @@ }, "show_all_live": { "type": "boolean", - "default": true, + "default": false, "description": "Show all live games, not just favorites" }, "favorite_live_boost": { diff --git a/plugins/soccer-scoreboard/manager.py b/plugins/soccer-scoreboard/manager.py index 7cfc5cd8..4711ac26 100644 --- a/plugins/soccer-scoreboard/manager.py +++ b/plugins/soccer-scoreboard/manager.py @@ -871,7 +871,7 @@ def _adapt_config_for_custom_league(self, custom_league: Dict[str, Any]) -> Dict "favorite_teams": custom_league.get("favorite_teams", []), "exclude_teams": custom_league.get("exclude_teams", []), "display_modes": manager_display_modes, - "recent_games_to_show": game_limits.get("recent_games_to_show", 5), + "recent_games_to_show": game_limits.get("recent_games_to_show", 1), # These ride the same source as the limits above, which is where the # schema declares them. Managers read a translated config, not the # plugin config, so a key missing here is a setting the user can @@ -882,7 +882,7 @@ def _adapt_config_for_custom_league(self, custom_league: Dict[str, Any]) -> Dict ), "other_recent_games_to_show": game_limits.get( "other_recent_games_to_show", - game_limits.get("recent_games_to_show", 5), + game_limits.get("recent_games_to_show", 1), ), "other_rotation_interval_seconds": game_limits.get( "other_rotation_interval_seconds", 1800 diff --git a/plugins/soccer-scoreboard/manifest.json b/plugins/soccer-scoreboard/manifest.json index f212c3a6..bab33e9c 100644 --- a/plugins/soccer-scoreboard/manifest.json +++ b/plugins/soccer-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "soccer-scoreboard", "name": "Soccer Scoreboard", - "version": "2.39.1", + "version": "2.39.2", "author": "ChuckBuilds", "description": "Live, recent, and upcoming soccer games across multiple leagues including Premier League, La Liga, Bundesliga, Serie A, Ligue 1, MLS, Liga Portugal, Champions League, Europa League, and FIFA World Cup", "category": "sports", @@ -53,6 +53,12 @@ "soccer_usa.1_upcoming" ], "versions": [ + { + "version": "2.39.2", + "released": "2026-10-02", + "ledmatrix_min_version": "3.8.0", + "notes": "A custom league whose game limits were never opened in the web UI showed 5 recent games instead of the 1 its settings page displays; it now uses the schema default of 1. The custom-league filtering.show_all_live default is now false in the schema, matching the built-in leagues and what the plugin already did, so the settings page no longer shows the switch on when it is off. README documents the per-league odds_update_interval / live_odds_update_interval and the full-screen scroll_card.switch_show_date / switch_show_time switches." + }, { "version": "2.39.1", "released": "2026-10-01", @@ -693,7 +699,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-30", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/static-image/README.md b/plugins/static-image/README.md index 8227c8e4..57f15567 100644 --- a/plugins/static-image/README.md +++ b/plugins/static-image/README.md @@ -238,8 +238,10 @@ Images outside their window are skipped by the rotation entirely. The common mistake is setting `mode` and the times but leaving `enabled` at `false`, which leaves the image always visible. -If every image is scheduled out at once there is nothing eligible to draw, so -keep at least one image unscheduled as a fallback. +If every image is outside its window at once, the schedules are set aside and +the whole list rotates as if none were scheduled — a per-image schedule never +blanks the panel. Keep at least one image unscheduled (or always in its window) +if you want to control what shows in the gaps. Windows are read in the LEDMatrix timezone from the main settings, not the Pi's system zone, so `08:00` means 08:00 where the panel is. The system clock @@ -266,6 +268,10 @@ image to use a wide panel, give it a wide source. `enabled` defaults to `false`. After that, check the `images` array is not empty and that at least one entry's `path` resolves. +**The panel shows "Image Error" in red.** +The current entry's file could not be found or decoded. The log names the path +it tried; check `path` as below. + **The image is missing but others show.** Check the log for a load warning. `path` is relative to the LEDMatrix project root, not to the plugin directory. @@ -330,8 +336,8 @@ python scripts/render_docs_assets.py --plugin static-image `--check` verifies the committed images still match what the plugin renders. The sample marks live in `docs/assets/static-image/sample/`. -Note that rotation cannot be shown in a still: every `rotation_mode` starts on -the first eligible image, and the differences only appear across frames. The +Note that rotation cannot be shown in a still: a screenshot catches one image, +and the differences between modes only appear across frames. The rotation behaviour above is documented from the source rather than from a screenshot. diff --git a/plugins/static-image/manager.py b/plugins/static-image/manager.py index 742ed719..b3607ed3 100644 --- a/plugins/static-image/manager.py +++ b/plugins/static-image/manager.py @@ -857,8 +857,12 @@ def _calculate_fit_size(self, image_size: Tuple[int, int], scale_x = display_width / img_width scale_y = display_height / img_height scale = min(scale_x, scale_y) - - return (int(img_width * scale), int(img_height * scale)) + + # A very wide or very tall source rounds its short side down to zero + # (2000x3 into 64x32 gives 64x0). Image.resize rejects a zero side, so + # the load failed and the panel showed "Image Error". One pixel is the + # smallest thing that can still be seen. + return (max(1, int(img_width * scale)), max(1, int(img_height * scale))) def update(self) -> None: """ @@ -1031,9 +1035,16 @@ def _display_error(self) -> None: (0, 0, 0)) if error_font: + from PIL import ImageDraw self.display_manager.image = img.copy() - self.display_manager.draw_text("Image", x=5, y=12, font=error_font, centered=False) - self.display_manager.draw_text("Error", x=5, y=20, font=error_font, centered=False) + # draw_text draws through display_manager.draw, which still + # pointed at the previous canvas: the text went onto an image + # nobody showed and the panel was pushed blank. The font + # manager does not draw with its registered colour either, so + # the red is passed here. + self.display_manager.draw = ImageDraw.Draw(self.display_manager.image) + self.display_manager.draw_text("Image", x=5, y=12, color=(255, 0, 0), font=error_font, centered=False) + self.display_manager.draw_text("Error", x=5, y=20, color=(255, 0, 0), font=error_font, centered=False) else: # Fallback to direct PIL if font manager fails from PIL import ImageDraw, ImageFont diff --git a/plugins/static-image/manifest.json b/plugins/static-image/manifest.json index 6a64607c..b1bf8833 100644 --- a/plugins/static-image/manifest.json +++ b/plugins/static-image/manifest.json @@ -1,7 +1,7 @@ { "id": "static-image", "name": "Static Image Display", - "version": "1.1.7", + "version": "1.1.8", "author": "ChuckBuilds", "description": "Display static images on your LED matrix with automatic scaling, aspect ratio preservation, and transparency support. Perfect for logos, artwork, or custom graphics.", "entry_point": "manager.py", @@ -18,6 +18,12 @@ "static_image" ], "versions": [ + { + "version": "1.1.8", + "released": "2026-10-02", + "ledmatrix_min_version": "2.0.0", + "notes": "The \"Image Error\" screen is visible. It swapped in a blank canvas and then drew its text through the display's previous drawing context, so the words landed on a frame nobody showed and the panel went blank whenever a font manager was present (always, on a board). It also drew in white; it is now red, as intended. A very wide or very tall image (say 2000x3) loads instead of erroring: fitting it rounded the short side down to zero, which Image.resize rejects. README: when every image is outside its schedule window the schedules are set aside and the whole list rotates (the old text said nothing would draw)." + }, { "version": "1.1.7", "released": "2026-09-28", @@ -112,7 +118,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/static-image/test_error_screen_and_fit.py b/plugins/static-image/test_error_screen_and_fit.py new file mode 100644 index 00000000..aadf9638 --- /dev/null +++ b/plugins/static-image/test_error_screen_and_fit.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +"""Regression tests: the "Image Error" screen is visible, and an extreme +aspect ratio still loads. + +1. _display_error() assigned a new blank canvas to display_manager.image and + then drew through display_manager.draw, which still pointed at the previous + canvas. The text went onto an image nobody showed and the panel was pushed + blank -- whenever a font manager was present, which is always on a real + board. It also passed no colour, so the text would have been white rather + than the red the plugin registers for errors. +2. _calculate_fit_size() rounded a very wide or very tall source's short side + down to zero (2000x3 into 64x32 gives 64x0); Image.resize rejects that, the + load failed, and the image read as an error. + +Run: LEDMATRIX_CORE=/path/to/LEDMatrix python plugins/static-image/test_error_screen_and_fit.py +Exit 0 pass, 2 skip (no core checkout), 1 fail. +""" + +import logging +import os +import sys +import tempfile +from pathlib import Path + +PLUGIN_DIR = Path(__file__).resolve().parent +sys.path.insert(0, str(PLUGIN_DIR)) +_core = os.environ.get("LEDMATRIX_CORE") +_candidates = [Path(_core)] if _core else [] +_candidates.append(PLUGIN_DIR.parents[2] / "LEDMatrix") +CORE = None +for candidate in _candidates: + if (candidate / "src" / "plugin_system" / "base_plugin.py").exists(): + CORE = candidate + sys.path.insert(0, str(candidate)) + break +if CORE is None: + print("SKIP: no LEDMatrix core checkout found (set LEDMATRIX_CORE)") + sys.exit(2) + +try: + from PIL import Image, ImageFont + from src.plugin_system.testing.visual_display_manager import VisualTestDisplayManager + import manager +except Exception as exc: + print("SKIP: missing dependency (%s)" % exc) + sys.exit(2) + +logging.disable(logging.CRITICAL) + +results = [] + + +def check(case, passed, detail=""): + results.append(passed) + print(f" [{'pass' if passed else 'FAIL'}] {case}" + ("" if passed or not detail else f" -- {detail}")) + + +class _FontManager: + """Stands in for the core FontManager: serves PressStart2P at any size.""" + + def register_manager_font(self, **kwargs): + pass + + def resolve_font(self, element_key=None, family=None, size_px=8): + return ImageFont.truetype(str(CORE / "assets" / "fonts" / "PressStart2P-Regular.ttf"), size_px) + + +class _PluginManager: + font_manager = _FontManager() + + +def make(images, w=64, h=32): + dm = VisualTestDisplayManager(w, h) + config = {"enabled": True, "images": images, "fit_to_display": True, + "preserve_aspect_ratio": True} + return dm, manager.StaticImagePlugin("static-image", config, dm, None, _PluginManager()) + + +print("the error screen") +dm, plugin = make([{"id": "gone", "path": "does/not/exist.png"}]) +# Leave a lit frame behind, as the previous plugin's screen would be. +dm.image = Image.new("RGB", (64, 32), (0, 0, 255)) +from PIL import ImageDraw # noqa: E402 +dm.draw = ImageDraw.Draw(dm.image) +plugin.display() +frame = dm.image.convert("RGB") +pixels = [frame.getpixel((x, y)) for y in range(frame.height) for x in range(frame.width)] +red = sum(1 for p in pixels if p[0] > 150 and p[1] < 80 and p[2] < 80) +blue = sum(1 for p in pixels if p[2] > 150 and p[0] < 80) +check("'Image Error' is drawn on the frame the panel shows", red > 20, f"{red} red pixels") +check("the previous screen is not left behind", blue == 0, f"{blue} blue pixels") + +print("an extreme aspect ratio") +tmp = Path(tempfile.mkdtemp(prefix="static-image-fit-")) +banner = tmp / "banner.png" +Image.new("RGB", (2000, 3), (0, 255, 0)).save(banner) +dm, plugin = make([{"id": "banner", "path": str(banner)}]) +check("a 2000x3 banner loads into 64x32", plugin.image_loaded) +check("the fit size never has a zero side", + min(plugin._calculate_fit_size((2000, 3), (64, 32))) >= 1 + and min(plugin._calculate_fit_size((3, 2000), (64, 32))) >= 1) + +print(f"\n{sum(results)}/{len(results)} passed") +sys.exit(0 if all(results) else 1) diff --git a/plugins/stock-news/README.md b/plugins/stock-news/README.md index 20659d00..9d5ac015 100644 --- a/plugins/stock-news/README.md +++ b/plugins/stock-news/README.md @@ -95,7 +95,7 @@ The full schema is [`config_schema.json`](config_schema.json). | Key | Default | Notes | |---|---|---| | `feeds.stock_symbols` | `["AAPL", "GOOGL", "MSFT"]` | Stock symbols — headlines fetched from Yahoo Finance. Fetches are spread across update_interval_seconds. | -| `feeds.custom_feeds` | *(empty)* | Extra RSS feeds to include alongside stock symbols. | +| `feeds.custom_feeds` | *(empty)* | Extra RSS feeds to include alongside stock symbols. Each entry is `{"name": ..., "url": ...}`; `name` is drawn as the story's label where a symbol would be. | | `feeds.text_color` | `"#00ff00"` | Color for headline text. | | `feeds.symbol_color` | `"#ffff00"` | Color for stock symbol labels (e.g. 'AAPL:'). | | `feeds.publisher_color` | `"#6e6e6e"` | Color for the publisher segment (e.g. '• Reuters'). Advanced. | diff --git a/plugins/stock-news/manifest.json b/plugins/stock-news/manifest.json index bcc98bb7..0340ed33 100644 --- a/plugins/stock-news/manifest.json +++ b/plugins/stock-news/manifest.json @@ -1,7 +1,7 @@ { "id": "stock-news", "name": "Stock News Ticker", - "version": "2.8.3", + "version": "2.8.4", "author": "ChuckBuilds", "description": "Live stock headlines via Yahoo Finance search API with RSS fallback, company logos, configurable display styles (logo+ticker, ticker only, logo only), Vegas scroll integration, and per-day/hour request budgeting", "entry_point": "manager.py", @@ -25,6 +25,12 @@ "branch": "main", "plugin_path": "plugins/stock-news", "versions": [ + { + "version": "2.8.4", + "released": "2026-10-02", + "notes": "Docs: the README describes a custom feed's name and url fields. No code change.", + "ledmatrix_min_version": "3.4.0" + }, { "version": "2.8.3", "released": "2026-10-01", @@ -196,7 +202,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/text-display/README.md b/plugins/text-display/README.md index 43025731..648151f3 100644 --- a/plugins/text-display/README.md +++ b/plugins/text-display/README.md @@ -183,9 +183,9 @@ coming round again. A gap roughly equal to your panel width gives the cleanest loop — the message is fully gone before it returns. The default of 32 suits a 64-wide panel; on a 128-wide chain, try 128. -With `scroll_loop: false` the text scrolls past once and stops. Pair it with -`display_duration` long enough for a full pass, or the plugin's turn will end -mid-message. +With `scroll_loop: false` the text scrolls past once and stops. The turn is +stretched to cover that pass (see [Timing](#timing)), and any time left over +holds the parked end frame. > **A still cannot show motion.** There is no screenshot of scrolling in this > README because a single frame captured at the start of a scroll is an empty @@ -213,19 +213,24 @@ deliberate alert, not as a default. | Option | Type | Default | What it does | |--------|------|---------|--------------| -| `display_duration` | number | `10` | Seconds the plugin holds the panel per turn | -| `update_interval` | integer | `60` | Seconds between refreshes of the text | +| `display_duration` | number | `10` | Seconds the plugin holds the panel per turn; the minimum while scrolling | +| `update_interval` | integer | `60` | Seconds between the core's calls to the plugin's `update()` | -For scrolling text, `display_duration` should be long enough for at least one -full pass, or viewers only ever see the middle of the message. A rough guide: +For scrolling text the plugin asks for a turn long enough for one full pass, +using `display_duration` as the floor and 300 seconds as the ceiling, so a long +message is not cut off mid-way. The turn it asks for is roughly: ```text -seconds for one pass ≈ (text width + panel width + scroll_gap_width) / (scroll_speed / scroll_delay) +seconds ≈ 1.1 × (text width + 3 × panel width + scroll_gap_width) / (scroll_speed / scroll_delay) ``` +Two exceptions: the first turn after a restart can come before the scrolling +strip has been built, and then uses `display_duration` alone; and a duration +set for this mode on the web UI's Rotation & Durations page replaces both. + `update_interval` matters little here because the text is static configuration -rather than fetched data — it only decides how quickly a config change is -picked up. +rather than fetched data. A settings save applies straight away whatever it is +set to. --- @@ -308,8 +313,10 @@ It is moved to the nearest speed the panel can draw in whole pixels — see [How fast it moves](#how-fast-it-moves). **I only ever see the middle of the message.** -`display_duration` is ending the turn before a full pass completes. Raise it, -or shorten the text. +The turn is ending before a full pass completes. The plugin stretches its turn +to one pass (up to 300 seconds), so check whether the Rotation & Durations page +sets a fixed duration for `text_display`, and whether the pass is longer than +300 seconds at your speed; raise the speed or shorten the text if so. **`font_size` has no effect.** Either `font_mode` is `auto` (which chooses the size itself) or `font_path` diff --git a/plugins/text-display/manifest.json b/plugins/text-display/manifest.json index aae06a81..6f0b4c5a 100644 --- a/plugins/text-display/manifest.json +++ b/plugins/text-display/manifest.json @@ -1,7 +1,7 @@ { "id": "text-display", "name": "Text Display", - "version": "1.3.2", + "version": "1.3.3", "author": "ChuckBuilds", "description": "Display custom scrolling or static text with configurable fonts, colors, and scroll speed. Perfect for announcements, messages, or custom displays.", "category": "display", @@ -24,6 +24,12 @@ } }, "versions": [ + { + "version": "1.3.3", + "released": "2026-10-02", + "ledmatrix_min_version": "3.4.0", + "notes": "README only. Scrolling text already stretches its turn to one full pass (display_duration is the floor, 300 seconds the ceiling), so the advice to raise display_duration to fit a pass, and the pass-length formula, were wrong; the timing section now describes what happens, including the Rotation & Durations override. update_interval does not decide how quickly a settings change is picked up: a save applies straight away." + }, { "version": "1.3.2", "released": "2026-09-28", @@ -115,7 +121,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/tide-display/README.md b/plugins/tide-display/README.md index 7dbb2546..3f1562b6 100644 --- a/plugins/tide-display/README.md +++ b/plugins/tide-display/README.md @@ -282,7 +282,7 @@ at the default 6 and `5x7.bdf` silently fell back. ```text tide-display/ ├── manifest.json # Plugin metadata and version history -├── manager.py # TideDisplayPlugin — all four screens +├── manager.py # TidePlugin — all four screens ├── config_schema.json # Settings schema; source of truth for defaults ├── requirements.txt └── README.md diff --git a/plugins/tide-display/manager.py b/plugins/tide-display/manager.py index 0e9d92e5..8d22f54a 100644 --- a/plugins/tide-display/manager.py +++ b/plugins/tide-display/manager.py @@ -427,7 +427,19 @@ def _prune_legacy_daily_keys(self): self._cache_delete(f"{self.plugin_id}:hilo:{self.station_id}:{d}") self._cache_delete(f"{self.plugin_id}:hourly:{self.station_id}:{d}") - def display(self, force_clear=False): + def display(self, display_mode=None, force_clear=False): + # The core rotates through the manifest's four modes and names the one + # it is showing. display() used to ignore the name and step its own + # rotation, so a screen switched off still took a turn (another screen + # was drawn in its slot). A mode that is off now reports no content, + # and the core moves straight on. Without a name (Vegas capture, older + # cores) the plugin keeps rotating on its own. + if display_mode in self.MODES: + if display_mode not in self.modes: + return False + mode = display_mode + else: + mode = self.modes[self.mode_idx % len(self.modes)] dw = self.display_manager.matrix.width dh = self.display_manager.matrix.height canvas = Image.new('RGB', (dw, dh), C_BG) @@ -442,7 +454,7 @@ def display(self, force_clear=False): elif not self.hilo: self._loading(draw, dw, dh, L) else: - m = self.modes[self.mode_idx % len(self.modes)] + m = mode if m == 'current': self._mode_current(canvas, draw, dw, dh, L) elif m == 'schedule': self._mode_schedule(draw, dw, dh, L) elif m == 'chart': self._mode_chart(canvas, draw, dw, dh, L) @@ -720,11 +732,14 @@ def _txt_s(self, x, y, text, color=C_TEXT, small=True): def _no_station(self, draw, dw, dh, L): draw.rectangle([0, 0, dw-1, dh-1], outline=C_BAR_OUT) - header_h, small_h, spacing = 8, 6, 2 - base_y = dh // 2 - 8 - line_y = [base_y + i * (small_h + spacing) + header_h + spacing + # Glyph heights as drawn: the header face is 7px, the small 4x6 face + # 7px with descenders. Sized as 6, the block ran onto the border on a + # 32-row panel and cut off the bottom of "in Settings". + header_h, small_h, spacing = 7, 7, 2 + block_h = header_h + spacing + 1 + small_h + spacing + small_h + base_y = max(1, (dh - block_h) // 2) + line_y = [base_y + header_h + spacing + 1 + i * (small_h + spacing) for i in range(2)] - line_y = [min(y, dh - small_h - 1) for y in line_y] self._txtc(dw//2, base_y, 'TIDE', C_WAVE1, small=False) self._txtc(dw//2, line_y[0], 'Set Station ID', C_LABEL) self._txtc(dw//2, line_y[1], 'in Settings', C_LABEL) diff --git a/plugins/tide-display/manifest.json b/plugins/tide-display/manifest.json index e64c4a70..c11014d6 100644 --- a/plugins/tide-display/manifest.json +++ b/plugins/tide-display/manifest.json @@ -1,7 +1,7 @@ { "id": "tide-display", "name": "Tide Display", - "version": "1.3.4", + "version": "1.3.5", "author": "ChuckBuilds", "description": "Coastal tide display with animated wave level, tide schedule, 24-hour chart, and tidal statistics. Powered by the free NOAA Tides & Currents API (US stations, no API key required).", "entry_point": "manager.py", @@ -25,6 +25,12 @@ ">=2.0.0" ], "versions": [ + { + "version": "1.3.5", + "released": "2026-10-02", + "ledmatrix_min_version": "2.0.0", + "notes": "A screen switched off no longer takes a turn: display() drew its own rotation and ignored the mode the core asked for, so a disabled screen's slot showed another screen. The setup prompt (no station set) no longer runs into the border on a 32-row panel, which cut off the bottom of 'in Settings'." + }, { "version": "1.3.4", "released": "2026-09-28", @@ -112,7 +118,7 @@ "license": "GPL-3.0", "homepage": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/tide-display", "config_schema": "config_schema.json", - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/tide-display/test_units_in_cache.py b/plugins/tide-display/test_units_in_cache.py index fa534397..721ca34d 100644 --- a/plugins/tide-display/test_units_in_cache.py +++ b/plugins/tide-display/test_units_in_cache.py @@ -57,6 +57,10 @@ def set(self, key, data, ttl=None): p.logger = logging.getLogger("test-tide") p.cache_manager = DictCache() p.STALE_MAX_DAYS = getattr(TidePlugin, "STALE_MAX_DAYS", 3) +# Pin "today" to the fixture dates below; the staleness check reads the +# real clock, so this case started failing a few days after it was written. +import datetime as _dt +p._today = lambda: _dt.date(2026, 9, 28) calls = [] diff --git a/plugins/ufc-scoreboard/CHANGELOG.md b/plugins/ufc-scoreboard/CHANGELOG.md index e6b4a92a..6089d88b 100644 --- a/plugins/ufc-scoreboard/CHANGELOG.md +++ b/plugins/ufc-scoreboard/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [1.19.3] - 2026-10-02 + +### Changed +- Root `update_interval` is hidden: the manifest declares `update_interval` + (60 s) and the core scheduler uses that over this setting, so it never did + anything. Each list refreshes at its own `ufc.*_update_interval`. Kept + declared so saved configs keep validating. The README said it set the fetch + cadence. +- README documents `ufc.odds_update_interval` and + `ufc.live_odds_update_interval`. + ## [1.19.2] - 2026-10-02 ### Changed diff --git a/plugins/ufc-scoreboard/README.md b/plugins/ufc-scoreboard/README.md index e29138ab..70904382 100644 --- a/plugins/ufc-scoreboard/README.md +++ b/plugins/ufc-scoreboard/README.md @@ -169,7 +169,7 @@ Defaults are the schema defaults, which is what the web UI writes. | `enabled` | boolean | `true` | Master on/off switch. | | `display_duration` | 5–300 s | `30` | How long the display controller shows this plugin's mode before rotating to the next plugin. | | `game_display_duration` | 3–60 s | `15` | Per-fight time within a mode, where the mode does not override it. | -| `update_interval` | 30–86400 s | `3600` | How often to fetch new data. | +| `update_interval` | 30–86400 s | `3600` | **Hidden (still declared); has no effect.** The manifest's `update_interval` (60 s) wins in the core scheduler, and each list refreshes at its own `ufc.*_update_interval`. | | `timezone` | string | `""` | **Advanced.** IANA zone for event times, e.g. `America/Chicago`. Blank follows the LEDMatrix global timezone, then the host system's, then UTC. | | `schedule_lookback_days` | 1–60 | `14` | **Advanced.** How far back to fetch for the Recent screen. | | `schedule_lookahead_days` | 1–60 | `7` | **Advanced.** How far ahead to fetch for Upcoming. A card beyond this horizon is never fetched. | @@ -218,6 +218,8 @@ Defaults are the schema defaults, which is what the web UI writes. | `ufc.live_update_interval` | 5–300 s | `30` | How often live fight data refreshes. | | `ufc.recent_update_interval` | 60–86400 s | `3600` | **Advanced.** How often the finished-fights list is rebuilt. This also sets how soon a fight that has just ended can appear. | | `ufc.upcoming_update_interval` | 60–86400 s | `3600` | **Advanced.** How often the upcoming-fights list is rebuilt. | +| `ufc.odds_update_interval` | 60–86400 s | `3600` | **Advanced.** How long fetched odds for a fight that has not started are reused before being requested again. | +| `ufc.live_odds_update_interval` | 15–3600 s | `60` | **Advanced.** The same for a fight in progress. | | `ufc.stale_game_timeout` | 60–3600 s | `300` | **Advanced.** Drop a live fight the API has stopped updating. | ### Dynamic duration @@ -323,7 +325,8 @@ can tell *that* a fight is live but not *whose*. ## Data source ESPN's public MMA endpoints. No API key required. Be mindful of -`update_interval` — the default of 3600s suits normal use. +`ufc.recent_update_interval` and `ufc.upcoming_update_interval` — the default +of 3600s suits normal use. The documentation images come from `docs/assets/ufc-scoreboard/shots.json` and re-render with `python scripts/render_docs_assets.py --plugin ufc-scoreboard diff --git a/plugins/ufc-scoreboard/config_schema.json b/plugins/ufc-scoreboard/config_schema.json index 444f34b7..6b994563 100644 --- a/plugins/ufc-scoreboard/config_schema.json +++ b/plugins/ufc-scoreboard/config_schema.json @@ -21,7 +21,9 @@ "default": 3600, "minimum": 30, "maximum": 86400, - "description": "How often to fetch new data in seconds" + "x-advanced": true, + "x-display": "hidden", + "description": "How often to fetch new data in seconds HIDDEN: the manifest declares update_interval (60 s), and the core scheduler uses the manifest's value over this setting; while a fight is live, get_update_interval() answers instead. Fetch cadence comes from ufc.live_update_interval, recent_update_interval and upcoming_update_interval." }, "game_display_duration": { "type": "number", diff --git a/plugins/ufc-scoreboard/manifest.json b/plugins/ufc-scoreboard/manifest.json index 9dfc8e35..c20bb73f 100644 --- a/plugins/ufc-scoreboard/manifest.json +++ b/plugins/ufc-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "ufc-scoreboard", "name": "UFC Scoreboard", - "version": "1.19.2", + "version": "1.19.3", "author": "LegoGuy1000", "contributors": [ { @@ -32,6 +32,12 @@ "default_duration": 15, "config_schema": "config_schema.json", "versions": [ + { + "version": "1.19.3", + "released": "2026-10-02", + "ledmatrix_min_version": "3.8.0", + "notes": "Hides the root update_interval setting: the manifest's update_interval (60 s) wins in the core scheduler, so it never did anything; each list refreshes at its own ufc.*_update_interval. README said it set the fetch cadence, and now documents ufc.odds_update_interval and live_odds_update_interval. No change in behaviour." + }, { "version": "1.19.2", "released": "2026-10-02", diff --git a/plugins/youtube-stats/manager.py b/plugins/youtube-stats/manager.py index 2cb378da..8540bb07 100644 --- a/plugins/youtube-stats/manager.py +++ b/plugins/youtube-stats/manager.py @@ -228,7 +228,9 @@ def _get_channel_stats(self) -> Optional[Dict[str, Any]]: return None # Try cache first - cache_key = f"{self.plugin_id}_channel_stats" + # Keyed by channel: with one shared key, switching channel_id served + # the previous channel's stats until the old entry aged out. + cache_key = f"{self.plugin_id}_channel_stats_{self.channel_id}" cached = self.cache_manager.get(cache_key, max_age=self.update_interval_config) if cached: self.logger.debug("Using cached channel stats") diff --git a/plugins/youtube-stats/manifest.json b/plugins/youtube-stats/manifest.json index 9ee21c25..70d3ab5b 100644 --- a/plugins/youtube-stats/manifest.json +++ b/plugins/youtube-stats/manifest.json @@ -1,7 +1,7 @@ { "id": "youtube-stats", "name": "YouTube Stats", - "version": "1.3.2", + "version": "1.3.3", "author": "ChuckBuilds", "description": "Display YouTube channel statistics including subscriber count, total views, and channel name on your LED matrix", "category": "social", @@ -33,6 +33,12 @@ } ], "versions": [ + { + "released": "2026-10-02", + "version": "1.3.3", + "changelog": "The cached channel stats are keyed by channel ID. With one shared key, changing channel_id kept showing the previous channel's numbers until the cached entry expired (up to update_interval).", + "ledmatrix_min_version": "3.4.0" + }, { "released": "2026-09-28", "version": "1.3.2", @@ -106,7 +112,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2026-09-28", + "last_updated": "2026-10-02", "compatible_versions": [ ">=3.4.0" ] diff --git a/scripts/test_stage3_mixin_copies.py b/scripts/test_stage3_mixin_copies.py index d900bc54..6d98f183 100644 --- a/scripts/test_stage3_mixin_copies.py +++ b/scripts/test_stage3_mixin_copies.py @@ -56,6 +56,7 @@ "SportsFetchMixin", "sports.py", "SportsCore", ALL, (), ("_fetch_season_directly", "_background_fetches_espn_ranges", "_needs_previous_day", "_wants_live_odds", + "_cached_schedule", "_retire_previous_window", "_schedule_cache_key", "_LOOKBACK_CUTOFF_HOUR", "_LIVE_ODDS_LOOKAHEAD")), "src.common.sports_celebration": ( "SportsCelebrationMixin", "sports.py", "SportsLive", From 9ebb221f51386f709cc5658d2cd750ebf8ae0f30 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Sat, 3 Oct 2026 11:52:00 -0400 Subject: [PATCH 2/2] cricket: narrow two silent excepts; mark afl test import nosemgrep Codacy flagged three issues new in this PR: - cricket manager _upcoming_has_started: try/except/continue around .timestamp() -> skip entries without a datetime instead. - cricket manager on_config_change: try/except/pass closing the old session -> only close a session that exists, log a failure at DEBUG. - afl test_afl_plugin.py: importlib.import_module(name) takes only the fixed module names the test passes; marked nosemgrep. Co-Authored-By: Claude Opus 5.5 --- plugins/afl-scoreboard/test_afl_plugin.py | 3 ++- plugins/cricket-scoreboard/manager.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/afl-scoreboard/test_afl_plugin.py b/plugins/afl-scoreboard/test_afl_plugin.py index ae5dc6cd..47d8c4a9 100644 --- a/plugins/afl-scoreboard/test_afl_plugin.py +++ b/plugins/afl-scoreboard/test_afl_plugin.py @@ -42,7 +42,8 @@ def _mod(name, **attrs): # ``requests.models`` (fetch_service) would fail to load. try: import importlib - m = importlib.import_module(name) + # name is one of the fixed module names passed below. + m = importlib.import_module(name) # nosemgrep except ImportError: m = None if m is None: diff --git a/plugins/cricket-scoreboard/manager.py b/plugins/cricket-scoreboard/manager.py index f995e9c1..bdd90cd7 100644 --- a/plugins/cricket-scoreboard/manager.py +++ b/plugins/cricket-scoreboard/manager.py @@ -248,11 +248,9 @@ def _upcoming_has_started(self, now: float) -> bool: upcoming = list(self.upcoming_matches) for m in upcoming: start = m.get("start_time_utc") - try: - ts = start.timestamp() - except Exception: + if not hasattr(start, "timestamp"): continue - if 0 <= now - ts <= 12 * 3600: + if 0 <= now - start.timestamp() <= 12 * 3600: return True return False @@ -603,10 +601,12 @@ def on_config_change(self, new_config: Dict[str, Any]) -> None: self._lock = lock # The rebuilt fetcher opened its own HTTP session; close the old one # rather than leak a connection pool per settings save. - try: - old_fetcher.session.close() - except Exception: - pass + old_session = getattr(old_fetcher, "session", None) + if old_session is not None: + try: + old_session.close() + except Exception as e: + self.logger.debug("Closing the previous HTTP session failed: %s", e) # Force a re-discovery + refetch on next update. self._last_update = 0.0