led-ticker-baseball 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- led_ticker_baseball-0.1.0/.gitignore +13 -0
- led_ticker_baseball-0.1.0/.pre-commit-config.yaml +16 -0
- led_ticker_baseball-0.1.0/CLAUDE.md +143 -0
- led_ticker_baseball-0.1.0/LICENSE +21 -0
- led_ticker_baseball-0.1.0/Makefile +18 -0
- led_ticker_baseball-0.1.0/PKG-INFO +408 -0
- led_ticker_baseball-0.1.0/README.md +380 -0
- led_ticker_baseball-0.1.0/docs/attendance.gif +0 -0
- led_ticker_baseball-0.1.0/docs/ball-emoji-hires.png +0 -0
- led_ticker_baseball-0.1.0/docs/ball-emoji.png +0 -0
- led_ticker_baseball-0.1.0/docs/promotions.gif +0 -0
- led_ticker_baseball-0.1.0/docs/roll-transition.gif +0 -0
- led_ticker_baseball-0.1.0/docs/scores.gif +0 -0
- led_ticker_baseball-0.1.0/docs/standings.gif +0 -0
- led_ticker_baseball-0.1.0/docs/statcast.gif +0 -0
- led_ticker_baseball-0.1.0/docs/superpowers/plans/2026-06-11-promotions-widget.md +1536 -0
- led_ticker_baseball-0.1.0/docs/superpowers/plans/2026-06-12-statcast-widget.md +1517 -0
- led_ticker_baseball-0.1.0/docs/superpowers/plans/2026-06-13-attendance-widget.md +1898 -0
- led_ticker_baseball-0.1.0/docs/superpowers/plans/2026-06-14-demo-gifs-and-readme-patterns.md +280 -0
- led_ticker_baseball-0.1.0/docs/superpowers/plans/2026-06-15-attendance-abbr-and-probe-dry.md +482 -0
- led_ticker_baseball-0.1.0/docs/superpowers/plans/2026-06-15-statcast-team-filter.md +624 -0
- led_ticker_baseball-0.1.0/docs/superpowers/specs/2026-06-10-promotions-widget-design.md +180 -0
- led_ticker_baseball-0.1.0/docs/superpowers/specs/2026-06-11-statcast-widget-design.md +202 -0
- led_ticker_baseball-0.1.0/docs/superpowers/specs/2026-06-13-attendance-widget-design.md +287 -0
- led_ticker_baseball-0.1.0/docs/superpowers/specs/2026-06-14-demo-gifs-and-readme-patterns-design.md +211 -0
- led_ticker_baseball-0.1.0/docs/superpowers/specs/2026-06-14-statcast-team-filter-design.md +138 -0
- led_ticker_baseball-0.1.0/docs/superpowers/specs/2026-06-15-attendance-abbr-and-probe-dry-design.md +121 -0
- led_ticker_baseball-0.1.0/pyproject.toml +60 -0
- led_ticker_baseball-0.1.0/src/led_ticker_baseball/__init__.py +34 -0
- led_ticker_baseball-0.1.0/src/led_ticker_baseball/attendance.py +590 -0
- led_ticker_baseball-0.1.0/src/led_ticker_baseball/emoji.py +199 -0
- led_ticker_baseball-0.1.0/src/led_ticker_baseball/promotions.py +431 -0
- led_ticker_baseball-0.1.0/src/led_ticker_baseball/scores.py +1609 -0
- led_ticker_baseball-0.1.0/src/led_ticker_baseball/standings.py +304 -0
- led_ticker_baseball-0.1.0/src/led_ticker_baseball/statcast.py +476 -0
- led_ticker_baseball-0.1.0/src/led_ticker_baseball/teams.py +218 -0
- led_ticker_baseball-0.1.0/src/led_ticker_baseball/transition.py +606 -0
- led_ticker_baseball-0.1.0/tests/conftest.py +41 -0
- led_ticker_baseball-0.1.0/tests/test_attendance.py +963 -0
- led_ticker_baseball-0.1.0/tests/test_emoji.py +49 -0
- led_ticker_baseball-0.1.0/tests/test_import_purity.py +29 -0
- led_ticker_baseball-0.1.0/tests/test_lazy_palette.py +43 -0
- led_ticker_baseball-0.1.0/tests/test_promotions.py +659 -0
- led_ticker_baseball-0.1.0/tests/test_scoreboard.py +1064 -0
- led_ticker_baseball-0.1.0/tests/test_scores.py +1782 -0
- led_ticker_baseball-0.1.0/tests/test_smoke.py +30 -0
- led_ticker_baseball-0.1.0/tests/test_standings.py +534 -0
- led_ticker_baseball-0.1.0/tests/test_statcast.py +1011 -0
- led_ticker_baseball-0.1.0/tests/test_teams.py +96 -0
- led_ticker_baseball-0.1.0/tests/test_transition.py +497 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.4.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
- repo: local
|
|
9
|
+
hooks:
|
|
10
|
+
- id: pyright
|
|
11
|
+
name: pyright
|
|
12
|
+
entry: uv run pyright src
|
|
13
|
+
language: system
|
|
14
|
+
pass_filenames: false
|
|
15
|
+
always_run: true
|
|
16
|
+
stages: [pre-push]
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Guidance for Claude Code when working in **led-ticker-baseball**, an external plugin for
|
|
4
|
+
[led-ticker](https://github.com/JamesAwesome/led-ticker).
|
|
5
|
+
|
|
6
|
+
`README.md` is the source of truth for the user-facing surface (widget options, team codes,
|
|
7
|
+
transition variants, install). This file keeps the **load-bearing invariants** a contributor
|
|
8
|
+
must respect, plus navigation aids. When a fact here and the README disagree about *how a
|
|
9
|
+
feature works*, the README wins; this file is the source of truth for *how to keep it working*.
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
This plugin contributes, via the `led_ticker.plugins` entry point, an MLB feature set that
|
|
14
|
+
used to live in led-ticker core (`type = "mlb"`):
|
|
15
|
+
|
|
16
|
+
- `baseball.scores` — live/final/preview scores; `ticker`, `scoreboard`, or `two_row` layout.
|
|
17
|
+
- `baseball.standings` — scrolling division standings (top-N + tracked teams), offseason-aware.
|
|
18
|
+
- `baseball.promotions` — upcoming home-game promotions (giveaways/theme nights); today-first with highlight/filter/limit knobs and offseason-aware fallbacks.
|
|
19
|
+
- `baseball.statcast` — daily Statcast superlatives (longest HR, hardest hit,
|
|
20
|
+
fastest/slowest pitch), league-wide or scoped to one team's players via an
|
|
21
|
+
optional `team`; from Baseball Savant's day CSV, schedule-gated.
|
|
22
|
+
- `baseball.attendance` — ballpark attendance: league-wide daily superlatives
|
|
23
|
+
(biggest/smallest crowd, fullest/emptiest park) or one team's game
|
|
24
|
+
(attendance + fill % + venue + weather); schedule-gated.
|
|
25
|
+
- `baseball.roll` / `baseball.roll_reverse` / `baseball.roll_alternating` — a rolling-baseball
|
|
26
|
+
sprite transition (lo-res 4-frame; procedural hi-res on the bigsign).
|
|
27
|
+
- `:baseball.ball:` — inline emoji (8×8 lo-res + 32×32 procedural hi-res).
|
|
28
|
+
|
|
29
|
+
The entry-point name `baseball` is the plugin namespace, so config `type`/transition/emoji
|
|
30
|
+
names are all `baseball.<name>` (see `register()` in `__init__.py`).
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
led-ticker is **not on PyPI**; it resolves from a sibling checkout via
|
|
35
|
+
`[tool.uv.sources] led-ticker = { path = "../led-ticker", editable = true }`. CI checks out
|
|
36
|
+
`led-ticker` next to this repo using a read-only deploy key (`LED_TICKER_DEPLOY_KEY`). The
|
|
37
|
+
sibling checkout matters at test time too: `pyproject.toml` puts `../led-ticker/tests/stubs`
|
|
38
|
+
on the pytest path so the rgbmatrix stub is importable headless.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv sync --extra dev # install deps (needs ../led-ticker checked out)
|
|
42
|
+
uv run pytest -q # full suite (asyncio_mode = "auto")
|
|
43
|
+
uv run ruff check src tests # lint — run before pushing
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Python **3.14+** only.
|
|
47
|
+
|
|
48
|
+
## Package layout
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
src/led_ticker_baseball/
|
|
52
|
+
__init__.py # register(api) entry point — the only place names are registered
|
|
53
|
+
emoji.py # :baseball.ball: — lo-res 8×8 (BALL) + procedural hi-res 32×32 (BALL_HIRES)
|
|
54
|
+
teams.py # shared MLB team colors/names/abbr tables, lazy palette, async resolve_team_id()
|
|
55
|
+
scores.py # baseball.scores widget (MLBScoreMonitor); ticker/scoreboard/two_row; game-state machine
|
|
56
|
+
standings.py # baseball.standings widget (MLBStandingsMonitor); top-N + tracked teams; offseason awareness
|
|
57
|
+
promotions.py # baseball.promotions widget (MLBPromotionsMonitor); home-game promos; today-first + fallback states
|
|
58
|
+
statcast.py # baseball.statcast widget (MLBStatcastMonitor); Savant day-CSV superlatives; schedule-gated
|
|
59
|
+
attendance.py # baseball.attendance widget (MLBAttendanceMonitor); league superlatives + team mode; schedule-gated
|
|
60
|
+
transition.py # baseball.roll* family; lo-res 4-frame + procedural hi-res rotation
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
All five widget modules import the shared tables from `teams.py` (no widget reaches into
|
|
64
|
+
another widget). `transition.py` reuses the hi-res sprite generator from
|
|
65
|
+
`emoji.py`. These sibling intra-package imports are allowed; see the import contract below.
|
|
66
|
+
|
|
67
|
+
`register(api)` (in `__init__.py`):
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
def register(api):
|
|
71
|
+
api.widget("scores")(MLBScoreMonitor)
|
|
72
|
+
api.widget("standings")(MLBStandingsMonitor)
|
|
73
|
+
api.widget("promotions")(MLBPromotionsMonitor)
|
|
74
|
+
api.widget("statcast")(MLBStatcastMonitor)
|
|
75
|
+
api.widget("attendance")(MLBAttendanceMonitor)
|
|
76
|
+
api.transition("roll")(Baseball)
|
|
77
|
+
api.transition("roll_reverse")(BaseballReverse)
|
|
78
|
+
api.transition("roll_alternating")(BaseballAlternating)
|
|
79
|
+
api.emoji("ball", BALL)
|
|
80
|
+
api.hires_emoji("ball", BALL_HIRES)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Load-bearing invariants
|
|
84
|
+
|
|
85
|
+
Each rule must hold when modifying the named area.
|
|
86
|
+
|
|
87
|
+
**Import only the public surface** — every `led_ticker` import MUST come from `led_ticker.plugin`,
|
|
88
|
+
never `led_ticker.<internal>`. Enforced by `tests/test_import_purity.py`, which AST-walks every
|
|
89
|
+
source file (catches `from`-imports *and* `import led_ticker.x` forms, not just a text grep).
|
|
90
|
+
Intra-package imports (`from led_ticker_baseball.teams import …`) are fine. If you need a core
|
|
91
|
+
symbol that isn't on `led_ticker.plugin.__all__`, that's a core API change — raise it upstream,
|
|
92
|
+
don't reach around the surface.
|
|
93
|
+
|
|
94
|
+
**Python 3.14 / PEP 649** — no `from __future__ import annotations` anywhere (same rule as core).
|
|
95
|
+
Bare `tuple[int, int, int]` annotations are fine. `ColorTuple` is defined locally in `teams.py`
|
|
96
|
+
because it isn't on the public surface.
|
|
97
|
+
|
|
98
|
+
**`validate_config()` contract** (`MLBScoreMonitor.validate_config`, `scores.py`) — a classmethod
|
|
99
|
+
run pre-coercion by the engine's `validate_widget_cfg`. It **returns `list[str]`** (does NOT raise);
|
|
100
|
+
the engine turns any returned message into a pre-flight `ValueError`. It reproduces the two
|
|
101
|
+
guardrails core formerly applied to `type = "mlb"`: (1) `layout` must be in
|
|
102
|
+
`("ticker", "scoreboard", "two_row")` (`_MLB_VALID_LAYOUTS`); (2) the per-row `top_*` knobs
|
|
103
|
+
(`_TWO_ROW_ONLY`) are rejected by name when `layout != "two_row"` — named, not silently ignored,
|
|
104
|
+
so stale configs surface.
|
|
105
|
+
|
|
106
|
+
**`teams.py` lazy palette is PEP 562** — module-level `__getattr__` exports the named colors
|
|
107
|
+
(`WIN_COLOR`/`LOSS_COLOR`/`LIVE_COLOR`/`CHALLENGE_COLOR`) so external code can
|
|
108
|
+
`from led_ticker_baseball.teams import WIN_COLOR`. **In-module use must call `_team_palette(name)`
|
|
109
|
+
directly** — PEP 562 `__getattr__` does NOT fire for bare-name lookups within the defining module.
|
|
110
|
+
|
|
111
|
+
**Team color lifting** (`_lift_color`, `teams.py`) — dark team colors are scaled so the peak RGB
|
|
112
|
+
channel is ≥ 120, keeping them legible on-panel at low brightness; hue/saturation are preserved
|
|
113
|
+
and already-bright teams are unchanged. Don't bypass it when adding team colors.
|
|
114
|
+
|
|
115
|
+
**Hi-res transition dispatch** — the `baseball.roll*` classes set `scale_switch_at = SNAP_THRESHOLD`
|
|
116
|
+
and branch on `is_scaled(canvas)` (bigsign / `ScaledCanvas`). The hi-res path paints physical LEDs
|
|
117
|
+
via `unwrap_to_real(canvas)` and snaps to incoming at `SNAP_THRESHOLD`. Sprite frames are 8
|
|
118
|
+
rotations at 45° (90° reads as alternating; 22.5° reads chaotic on small panels) and are
|
|
119
|
+
`@functools.cache`'d — geometry is deterministic. `is_scaled` / `unwrap_to_real` / `snap_reset` /
|
|
120
|
+
`SNAP_THRESHOLD` all come from `led_ticker.plugin`; don't hand-copy them back in.
|
|
121
|
+
|
|
122
|
+
**emoji ↔ transition coupling** — `transition.py` imports `_generate_baseball_hires` from
|
|
123
|
+
`emoji.py` **inside a function**, not at module top, to avoid a circular import. Keep it lazy.
|
|
124
|
+
|
|
125
|
+
## Tests / CI
|
|
126
|
+
|
|
127
|
+
`uv run pytest -q` runs the suite (`tests/`):
|
|
128
|
+
|
|
129
|
+
- `test_import_purity.py` — the AST tripwire (public-surface-only). Treat a failure as a contract
|
|
130
|
+
violation, not a test to relax.
|
|
131
|
+
- `test_smoke.py` — loads the plugin through led-ticker's real plugin loader and asserts the
|
|
132
|
+
widgets/transitions/emoji register under the `baseball.*` namespace (entry-point wiring guard).
|
|
133
|
+
- `test_scores.py` / `test_scoreboard.py` / `test_standings.py` / `test_promotions.py` /
|
|
134
|
+
`test_statcast.py` / `test_attendance.py` / `test_transition.py` / `test_emoji.py` / `test_lazy_palette.py` — behavior + rendering coverage.
|
|
135
|
+
|
|
136
|
+
CI (`.github/workflows/ci.yml`): checks out this repo + led-ticker as siblings (deploy key),
|
|
137
|
+
Python 3.14, `uv sync --extra dev`, then `ruff check src tests` and `pytest -q`.
|
|
138
|
+
|
|
139
|
+
## Adding to the plugin
|
|
140
|
+
|
|
141
|
+
Register the class in `register()` in `__init__.py` (`api.widget` / `api.transition` /
|
|
142
|
+
`api.emoji` / `api.hires_emoji`); it becomes `baseball.<name>`. Import any core dependency from
|
|
143
|
+
`led_ticker.plugin` only, and keep the import-purity test green.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 James Awesome
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
.PHONY: dev test lint format typecheck
|
|
2
|
+
|
|
3
|
+
dev: ## Install dev deps + pre-commit hooks
|
|
4
|
+
uv sync --extra dev
|
|
5
|
+
uv run pre-commit install
|
|
6
|
+
uv run pre-commit install --hook-type pre-push
|
|
7
|
+
|
|
8
|
+
test: ## Run tests with coverage
|
|
9
|
+
uv run pytest --cov=src --cov-report=term-missing
|
|
10
|
+
|
|
11
|
+
lint: ## Ruff lint
|
|
12
|
+
uv run ruff check src tests
|
|
13
|
+
|
|
14
|
+
format: ## Ruff format
|
|
15
|
+
uv run ruff format src tests
|
|
16
|
+
|
|
17
|
+
typecheck: ## Pyright
|
|
18
|
+
uv run pyright src
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: led-ticker-baseball
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MLB scores/standings widgets, baseball emoji, and baseball transitions for led-ticker.
|
|
5
|
+
Project-URL: Homepage, https://docs.ledticker.dev
|
|
6
|
+
Project-URL: Repository, https://github.com/JamesAwesome/led-ticker-plugins
|
|
7
|
+
Project-URL: Issues, https://github.com/JamesAwesome/led-ticker-plugins/issues
|
|
8
|
+
Author-email: James Awesome <james@morelli.nyc>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
16
|
+
Requires-Python: >=3.14
|
|
17
|
+
Requires-Dist: aiohttp
|
|
18
|
+
Requires-Dist: led-ticker-core>=2.0
|
|
19
|
+
Requires-Dist: pillow
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pre-commit>=4.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: pyright>=1.1; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# led-ticker-baseball
|
|
30
|
+
|
|
31
|
+
MLB scores, standings, promotions, Statcast, and attendance widgets, a rolling-baseball sprite transition, and a `:baseball.ball:` emoji for [led-ticker](https://github.com/JamesAwesome/led-ticker). Live game data comes from MLB's free StatsAPI — no API key required.
|
|
32
|
+
|
|
33
|
+
## Screenshots
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
The `:baseball.ball:` emoji (8×8 and the 32×32 hi-res upgrade on bigsign):
|
|
48
|
+
|
|
49
|
+
 
|
|
50
|
+
|
|
51
|
+
## Prerequisites
|
|
52
|
+
|
|
53
|
+
- A working [led-ticker](https://github.com/JamesAwesome/led-ticker) install.
|
|
54
|
+
- Internet access on the Pi (the widgets call MLB's free StatsAPI; no API key needed).
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
This plugin auto-registers via the `led_ticker.plugins` entry point — once the package is installed, no `[plugins]` config change is needed.
|
|
59
|
+
|
|
60
|
+
**Into a containerized led-ticker (recommended):** the plugin is already listed in `config/requirements-plugins.example.txt`. Copy that to the live file and rebuild:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# in your led-ticker checkout
|
|
64
|
+
cp config/requirements-plugins.example.txt config/requirements-plugins.txt
|
|
65
|
+
docker compose up -d --build
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
That example file lists every first-party plugin — trim the live copy to just the ones you want. The baseball line is:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
git+https://github.com/JamesAwesome/led-ticker-baseball.git@main
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Standalone (a venv that already has led-ticker):**
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install "git+https://github.com/JamesAwesome/led-ticker-baseball.git@main"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
led-ticker isn't on PyPI, so this path only works where led-ticker is already installed. See the led-ticker [Plugins docs](https://docs.ledticker.dev/plugins/) for the constraint-based install the Docker image uses.
|
|
81
|
+
|
|
82
|
+
Once installed, the `baseball.scores` / `baseball.standings` / `baseball.promotions` / `baseball.statcast` / `baseball.attendance` widgets, the `baseball.roll*` transitions, and the `:baseball.ball:` emoji are available automatically.
|
|
83
|
+
|
|
84
|
+
## Widgets
|
|
85
|
+
|
|
86
|
+
Each widget below is a `[[playlist.section.widget]]` block you add inside a playlist section of your `config/config.toml`. New to led-ticker configs? The [first-config tutorial](https://docs.ledticker.dev/tutorial/02-first-config/) walks through the overall structure — the blocks here show just the baseball-specific keys.
|
|
87
|
+
|
|
88
|
+
### `baseball.scores`
|
|
89
|
+
|
|
90
|
+
Fetches live game state for a tracked team and renders its current series. Three layouts:
|
|
91
|
+
|
|
92
|
+
- **`layout = "ticker"` (default)** — a scrolling line. Pre-game `NYY @ BOS Today 7:05 PM`; live `NYY 3 BOS 5 ▲6 ◇◆◇ 1·2·1` (score + inning + bases + balls·strikes·outs in color); final `NYY 4 BOS 5 (Final)` (win green, loss red); postponed `NYY @ BOS (PPD: Rain)`. Spring Training / All-Star games append `(ST)` / `(ASG)`.
|
|
93
|
+
- **`layout = "scoreboard"`** — a two-column board for bigsign/longboi: away name+score left, home name+score right, center zone shows inning+outs (top) and B/S count + base diamonds (bottom). Names in brand colors; scores green/red on final; base diamonds yellow (occupied) / dim grey (empty). ABS-challenge dashes appear in the bottom corners when active.
|
|
94
|
+
- **`layout = "two_row"`** — a held top band (series title) over a scrolling bottom band (the per-game line). Use the `top_*` font options below to size the top band; sized for bigsign.
|
|
95
|
+
|
|
96
|
+
```toml
|
|
97
|
+
[[playlist.section.widget]]
|
|
98
|
+
type = "baseball.scores"
|
|
99
|
+
team = "NYY"
|
|
100
|
+
timezone = "America/New_York" # set to your local timezone
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**`team` is the only required field** — everything below is optional tuning.
|
|
104
|
+
|
|
105
|
+
| Option | Type | Default | Description |
|
|
106
|
+
|--------|------|---------|-------------|
|
|
107
|
+
| `team` | string | required | MLB team abbreviation, 2–3 letters (e.g. `"NYY"`, `"KC"`, `"SD"`) — see [Team codes](#team-codes). Case-insensitive. |
|
|
108
|
+
| `layout` | string | `"ticker"` | `"ticker"`, `"scoreboard"`, or `"two_row"`. |
|
|
109
|
+
| `timezone` | string | `"America/New_York"` | IANA timezone for game-time formatting. |
|
|
110
|
+
| `padding` | int | `6` | Horizontal padding (logical px) after each message when scrolling (ticker). |
|
|
111
|
+
| `final_hold_hours` | int | `6` | Hours after a game ends to keep showing the final score. |
|
|
112
|
+
| `bg_color` | RGB list | none | Background fill behind all game messages. |
|
|
113
|
+
| `font_color` | RGB list / string / table | unset | Override all text color; default keeps per-segment brand/win-loss colors. |
|
|
114
|
+
| `font` | string | `"6x12"` | Font for names and scores. Hires name (e.g. `"Inter-Regular"`) needs `font_size`. |
|
|
115
|
+
| `font_size` | int | none | Point size; required for a hires (TTF/OTF) `font`. |
|
|
116
|
+
| `font_threshold` | int | `128` | Hires anti-alias threshold (0–255); `80` suits Inter Regular. |
|
|
117
|
+
| `small_font` | string | same as `font` | Center-zone font (scoreboard layout). |
|
|
118
|
+
| `small_font_size` | int | none | Point size for `small_font`. |
|
|
119
|
+
| `small_font_threshold` | int | same as `font_threshold` | Anti-alias threshold for `small_font`. |
|
|
120
|
+
| `top_font` | string | same as `font` | Top-band font (`two_row` layout only). |
|
|
121
|
+
| `top_font_size` | int | none | Point size for `top_font` (`two_row` only). |
|
|
122
|
+
| `top_font_threshold` | int | same as `font_threshold` | Anti-alias threshold for `top_font` (`two_row` only). |
|
|
123
|
+
| `top_row_height` | int | half the canvas | Height (logical px) of the held top band (`two_row` only). |
|
|
124
|
+
| `update_interval` | int | `300` | Seconds between StatsAPI fetches. |
|
|
125
|
+
|
|
126
|
+
> `top_*` options apply only with `layout = "two_row"` — the widget rejects them at config-load under other layouts.
|
|
127
|
+
|
|
128
|
+
### `baseball.standings`
|
|
129
|
+
|
|
130
|
+
Fetches overall MLB standings and scrolls them as `rank. TeamName W-L GB`, each name in its brand color. Shows the top-N plus any tracked `teams` not already in that list. Offseason-aware: before the season starts it shows `Opens Mar 27`; between the World Series and Spring Training it keeps the prior final standings.
|
|
131
|
+
|
|
132
|
+
```toml
|
|
133
|
+
[[playlist.section.widget]]
|
|
134
|
+
type = "baseball.standings"
|
|
135
|
+
teams = ["NYY", "BOS"]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**`teams` is the only required field** — everything below is optional.
|
|
139
|
+
|
|
140
|
+
| Option | Type | Default | Description |
|
|
141
|
+
|--------|------|---------|-------------|
|
|
142
|
+
| `teams` | list of strings | required | Tracked team abbreviations (e.g. `["NYY", "BOS"]`); always shown even outside top-N. |
|
|
143
|
+
| `top_n` | int | `3` | Overall top teams to show before tracked teams. `0` = tracked only. |
|
|
144
|
+
| `title` | string | `"MLB Standings"` | Section header before the list. |
|
|
145
|
+
| `timezone` | string | `"America/New_York"` | IANA timezone for offseason detection / opening-day date. |
|
|
146
|
+
| `padding` | int | `6` | Horizontal padding (logical px) after each message. |
|
|
147
|
+
| `bg_color` | RGB list | none | Background fill behind the standings. |
|
|
148
|
+
| `font_color` | RGB list / string / table | unset | Override all text color; default keeps rank white + team brand colors. |
|
|
149
|
+
| `font` | string | `"6x12"` | BDF or hires font for standings text. |
|
|
150
|
+
| `update_interval` | int | `86400` | Seconds between fetches (24 h default; standings move slowly). |
|
|
151
|
+
|
|
152
|
+
### `baseball.promotions`
|
|
153
|
+
|
|
154
|
+
Upcoming home-game promotions — giveaways and theme nights, e.g. the Blue Jays'
|
|
155
|
+
Loonie Dogs Night — for a tracked team, from the schedule API's promotions feed.
|
|
156
|
+
Shows today's promos when there's a home game today, otherwise the next home
|
|
157
|
+
game's, one scrolling line per promo led by the team abbreviation in its brand
|
|
158
|
+
color, with a grey date prefix: `TOR Jun 22 · Retro Domer Hat Giveaway`. Sponsor tails ("presented by …") are
|
|
159
|
+
stripped, and near-duplicate feed entries are collapsed. Promos matching
|
|
160
|
+
`highlight` render in amber and sort first.
|
|
161
|
+
|
|
162
|
+
```toml
|
|
163
|
+
[[playlist.section.widget]]
|
|
164
|
+
type = "baseball.promotions"
|
|
165
|
+
team = "TOR"
|
|
166
|
+
highlight = ["Loonie Dogs"]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**`team` is the only required field** — everything below is optional tuning.
|
|
170
|
+
|
|
171
|
+
| Option | Type | Default | Description |
|
|
172
|
+
|--------|------|---------|-------------|
|
|
173
|
+
| `team` | string | required | MLB team abbreviation — see [Team codes](#team-codes). Case-insensitive. |
|
|
174
|
+
| `highlight` | list of strings | `[]` | Case-insensitive substrings; matching promos render amber and sort first. |
|
|
175
|
+
| `filter` | list of strings | `[]` | If non-empty, only promos matching one of these substrings are shown. |
|
|
176
|
+
| `limit` | int | `0` | Max promo lines (`0` = all). Applied after highlight sorting, so highlighted promos are never the ones dropped. |
|
|
177
|
+
| `lookahead_days` | int | `14` | How far ahead to look for the next home game with promotions. |
|
|
178
|
+
| `update_interval` | int | `21600` | Seconds between refreshes (6 h — keeps the "Today" label honest after midnight). |
|
|
179
|
+
| `title` | string | `"<Team> Promos"` | Section title override. |
|
|
180
|
+
| `timezone` | string | `"America/New_York"` | IANA timezone governing "Today" and date labels. |
|
|
181
|
+
| `padding` | int | `6` | Horizontal padding (logical px) after each message when scrolling. |
|
|
182
|
+
| `bg_color` | RGB list | none | Background fill behind all messages. |
|
|
183
|
+
| `font_color` | RGB list / string / table | unset | RGB list tints the promo names; the team prefix, date label, and amber highlights keep their callout colors. A string/table provider overrides all text, as in the other widgets. |
|
|
184
|
+
| `font` | string | `"6x12"` | Display font. Hires name needs `font_size`. |
|
|
185
|
+
|
|
186
|
+
With nothing to show, the widget falls back to a team-prefixed
|
|
187
|
+
`Next home game: Jun 22` (promo-free homestand), `No home games soon`
|
|
188
|
+
(road trip), or `Opens <date>` / `Opens soon` (offseason).
|
|
189
|
+
|
|
190
|
+
### `baseball.statcast`
|
|
191
|
+
|
|
192
|
+
League-wide daily Statcast superlatives — the longest home run, hardest-hit
|
|
193
|
+
ball, and fastest/slowest pitch across all of MLB — or, with a `team` set, the
|
|
194
|
+
same superlatives scoped to that team's own players.
|
|
195
|
+
Re-derived through the day as games progress. One scrolling line per stat with
|
|
196
|
+
the value in amber and the record holder's team abbreviation in its brand color:
|
|
197
|
+
`Today · Longest HR 463 ft — Butler OAK`. Mornings fall back to yesterday's
|
|
198
|
+
finals, labeled with the short date (`6/12 · …`). Data comes from Baseball
|
|
199
|
+
Savant's day CSV (an
|
|
200
|
+
undocumented endpoint — the widget refreshes at a polite default cadence and
|
|
201
|
+
skips the pull entirely when no games are live or newly final).
|
|
202
|
+
|
|
203
|
+
```toml
|
|
204
|
+
[[playlist.section.widget]]
|
|
205
|
+
type = "baseball.statcast"
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**No required fields** — everything below is optional tuning.
|
|
209
|
+
|
|
210
|
+
| Option | Type | Default | Description |
|
|
211
|
+
|--------|------|---------|-------------|
|
|
212
|
+
| `team` | string | unset | Scope superlatives to this team's own players (e.g. a Phillies batter for `longest_hr`, a Phillies pitcher for `fastest_pitch`). Omit for league-wide. Case-insensitive — see [Team codes](#team-codes). |
|
|
213
|
+
| `stats` | list of strings | all four | Which lines to show, in display order: `"longest_hr"`, `"hardest_hit"`, `"fastest_pitch"`, `"slowest_pitch"`. |
|
|
214
|
+
| `update_interval` | int | `1800` | Seconds between refreshes (30 min). A ~10 KB schedule check skips the ~3 MB data pull when nothing changed. |
|
|
215
|
+
| `title` | string | `"Statcast"` | Section title override. |
|
|
216
|
+
| `timezone` | string | `"America/New_York"` | IANA timezone governing "Today" and the day rollover. |
|
|
217
|
+
| `padding` | int | `6` | Horizontal padding (logical px) after each message when scrolling. |
|
|
218
|
+
| `bg_color` | RGB list | none | Background fill behind all messages. |
|
|
219
|
+
| `font_color` | RGB list / string / table | unset | RGB list tints the stat label and name; the day label, amber value, and team abbr keep their callout colors. A string/table provider overrides all text, as in the other widgets. |
|
|
220
|
+
| `font` | string | `"6x12"` | Display font. Hires name needs `font_size`. |
|
|
221
|
+
|
|
222
|
+
The slowest-pitch line appends the pitch name when known (`69.6 mph (Slow
|
|
223
|
+
Curve)`) — that's where the eephus and position-player pitching comedy lives.
|
|
224
|
+
With no Statcast data for today or yesterday, the widget falls back to
|
|
225
|
+
`Next games: Mar 26` (offseason) or `No games soon`; a fetch failure shows
|
|
226
|
+
`No Data`.
|
|
227
|
+
|
|
228
|
+
With a team set, lines lead with the team abbreviation in its brand color and
|
|
229
|
+
drop the (now-redundant) trailing one:
|
|
230
|
+
`PHI Today · Longest HR 472 ft — Schwarber`. The off-day fallback then names
|
|
231
|
+
the team's next game (`Next game: Jun 20`) rather than the league slate.
|
|
232
|
+
|
|
233
|
+
### `baseball.attendance`
|
|
234
|
+
|
|
235
|
+
Ballpark attendance and conditions. Two modes, chosen by whether you set a
|
|
236
|
+
`team`:
|
|
237
|
+
|
|
238
|
+
- **League-wide** (no `team`): the day's attendance superlatives —
|
|
239
|
+
`Today · Biggest crowd 45,123 — Dodger Stadium`, plus smallest crowd and
|
|
240
|
+
fullest/emptiest park by capacity %. Venue name in the home team's brand
|
|
241
|
+
color.
|
|
242
|
+
- **Team** (`team` set): that team's game —
|
|
243
|
+
`TOR · Rogers Centre 41,212 (90%) · 72° Clear, wind 5 mph, In From CF`.
|
|
244
|
+
Attendance and fill % appear once the game is final; venue and weather show
|
|
245
|
+
before that.
|
|
246
|
+
|
|
247
|
+
```toml
|
|
248
|
+
[[playlist.section.widget]]
|
|
249
|
+
type = "baseball.attendance"
|
|
250
|
+
# team = "TOR" # set for team mode; omit for league-wide
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**No required fields** — everything is optional tuning.
|
|
254
|
+
|
|
255
|
+
| Option | Type | Default | Description |
|
|
256
|
+
|--------|------|---------|-------------|
|
|
257
|
+
| `team` | string | unset | Set → that team's game; omit → league-wide superlatives. |
|
|
258
|
+
| `stats` | list of strings | all four | League mode only, in display order: `"biggest_crowd"`, `"smallest_crowd"`, `"fullest"`, `"emptiest"`. |
|
|
259
|
+
| `update_interval` | int | `1800` | Seconds between refreshes (30 min). A ~47 KB schedule check skips the per-game fetches when nothing changed. |
|
|
260
|
+
| `title` | string | `"Attendance"` | Section title override. |
|
|
261
|
+
| `timezone` | string | `"America/New_York"` | IANA timezone for "Today" / day rollover. |
|
|
262
|
+
| `padding` | int | `6` | Horizontal padding (logical px) after each message. |
|
|
263
|
+
| `bg_color` | RGB list | none | Background fill behind all messages. |
|
|
264
|
+
| `font_color` | RGB list / string / table | unset | RGB list tints body text; the day label, amber value, and venue/team color keep their callout colors. A string/table provider overrides all text. |
|
|
265
|
+
| `font` | string | `"6x12"` | Display font. Hires name needs `font_size`. |
|
|
266
|
+
|
|
267
|
+
Fill % is omitted when a venue lists no capacity (spring sites). With nothing
|
|
268
|
+
final yet, the widget shows yesterday's data (short-date labeled, e.g.
|
|
269
|
+
`6/12 · …`); with no games at all it shows `Next game: Jun 20` (team) /
|
|
270
|
+
`Next games: Jun 20` (league); a fetch failure shows `No Data`.
|
|
271
|
+
|
|
272
|
+
## Common patterns
|
|
273
|
+
|
|
274
|
+
Recipes that combine the widgets above. Each block shows just the
|
|
275
|
+
baseball-specific keys — drop the widgets into a playlist section of your
|
|
276
|
+
`config/config.toml` (see the [first-config tutorial](https://docs.ledticker.dev/tutorial/02-first-config/) for the surrounding structure).
|
|
277
|
+
|
|
278
|
+
### My-team dashboard
|
|
279
|
+
|
|
280
|
+
One team across every widget, rotating with the rolling-baseball transition.
|
|
281
|
+
|
|
282
|
+
```toml
|
|
283
|
+
[[playlist.section]]
|
|
284
|
+
mode = "swap"
|
|
285
|
+
transition = "baseball.roll_alternating"
|
|
286
|
+
hold_time = 8
|
|
287
|
+
|
|
288
|
+
[[playlist.section.widget]]
|
|
289
|
+
type = "baseball.scores"
|
|
290
|
+
team = "TOR"
|
|
291
|
+
|
|
292
|
+
[[playlist.section.widget]]
|
|
293
|
+
type = "baseball.standings"
|
|
294
|
+
teams = ["TOR"]
|
|
295
|
+
|
|
296
|
+
[[playlist.section.widget]]
|
|
297
|
+
type = "baseball.promotions"
|
|
298
|
+
team = "TOR"
|
|
299
|
+
|
|
300
|
+
[[playlist.section.widget]]
|
|
301
|
+
type = "baseball.attendance"
|
|
302
|
+
team = "TOR"
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Shows your team's current series, its place in the standings, its next
|
|
306
|
+
home-game promotions, and the crowd and conditions at its game.
|
|
307
|
+
|
|
308
|
+
### League roundup
|
|
309
|
+
|
|
310
|
+
League-wide daily superlatives — omit `team` and both widgets run in league
|
|
311
|
+
mode.
|
|
312
|
+
|
|
313
|
+
```toml
|
|
314
|
+
[[playlist.section]]
|
|
315
|
+
mode = "swap"
|
|
316
|
+
hold_time = 8
|
|
317
|
+
scroll_step_ms = 35
|
|
318
|
+
|
|
319
|
+
[[playlist.section.widget]]
|
|
320
|
+
type = "baseball.statcast"
|
|
321
|
+
|
|
322
|
+
[[playlist.section.widget]]
|
|
323
|
+
type = "baseball.attendance"
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Shows the day's longest home run, hardest-hit ball, and fastest and slowest
|
|
327
|
+
pitch, then the biggest and smallest crowd and the fullest and emptiest park
|
|
328
|
+
across all of MLB.
|
|
329
|
+
|
|
330
|
+
### Gameday ticker
|
|
331
|
+
|
|
332
|
+
A minimal single-team scrolling line.
|
|
333
|
+
|
|
334
|
+
```toml
|
|
335
|
+
[[playlist.section]]
|
|
336
|
+
mode = "swap"
|
|
337
|
+
hold_time = 6
|
|
338
|
+
|
|
339
|
+
[[playlist.section.widget]]
|
|
340
|
+
type = "baseball.scores"
|
|
341
|
+
team = "NYY"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Shows just the tracked team's live, final, or upcoming game line.
|
|
345
|
+
|
|
346
|
+
### Shared knobs
|
|
347
|
+
|
|
348
|
+
- Every widget accepts the standard `title`, `font`, `font_color`, `bg_color`,
|
|
349
|
+
`padding`, and `timezone` options — see each widget's table above.
|
|
350
|
+
- Put `:baseball.ball:` in a `[playlist.section.title]` message for a themed
|
|
351
|
+
header.
|
|
352
|
+
- **Pacing** is tuned with `hold_time` (dwell before and after a line) and
|
|
353
|
+
`scroll_step_ms` (scroll cadence — lower is faster). These are
|
|
354
|
+
[led-ticker section settings](https://docs.ledticker.dev/), not plugin
|
|
355
|
+
options; they control how an overflowing line (common in the statcast,
|
|
356
|
+
attendance, and promotions widgets) reads on the panel.
|
|
357
|
+
|
|
358
|
+
## Team codes
|
|
359
|
+
|
|
360
|
+
All 30 teams (used by the scores, standings, promotions, statcast, and attendance widgets):
|
|
361
|
+
|
|
362
|
+
`ARI` D-backs · `ATL` Braves · `BAL` Orioles · `BOS` Red Sox · `CHC` Cubs · `CIN` Reds · `CLE` Guardians · `COL` Rockies · `CWS` White Sox · `DET` Tigers · `HOU` Astros · `KC` Royals · `LAA` Angels · `LAD` Dodgers · `MIA` Marlins · `MIL` Brewers · `MIN` Twins · `NYM` Mets · `NYY` Yankees · `OAK` Athletics · `PHI` Phillies · `PIT` Pirates · `SD` Padres · `SEA` Mariners · `SF` Giants · `STL` Cardinals · `TB` Rays · `TEX` Rangers · `TOR` Blue Jays · `WSH` Nationals
|
|
363
|
+
|
|
364
|
+
## Transition
|
|
365
|
+
|
|
366
|
+
A rolling-baseball sprite transition, registered in three directions:
|
|
367
|
+
|
|
368
|
+
```toml
|
|
369
|
+
transition = "baseball.roll" # left-to-right
|
|
370
|
+
# transition = "baseball.roll_reverse" # right-to-left
|
|
371
|
+
# transition = "baseball.roll_alternating" # alternates each use
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
On a bigsign panel (`default_scale > 1`) the transition automatically renders a hi-res procedurally-rotated ball; on a smallsign it uses the 8-frame lo-res sprite.
|
|
375
|
+
|
|
376
|
+
## Emoji
|
|
377
|
+
|
|
378
|
+
`:baseball.ball:` — a white ball with red stitching. Use it inline in any text-bearing widget:
|
|
379
|
+
|
|
380
|
+
```toml
|
|
381
|
+
[[playlist.section.widget]]
|
|
382
|
+
type = "message"
|
|
383
|
+
text = ":baseball.ball: Play ball!"
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
It renders as an 8×8 sprite, auto-upgrading to a 32×32 hi-res sprite on bigsign.
|
|
387
|
+
|
|
388
|
+
## Development
|
|
389
|
+
|
|
390
|
+
led-ticker isn't on PyPI, so this plugin resolves it from a sibling checkout. Clone both side by side:
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
~/projects/.../led-ticker
|
|
394
|
+
~/projects/.../led-ticker-baseball
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
uv sync --extra dev # resolves led-ticker from ../led-ticker
|
|
399
|
+
uv run pytest -q
|
|
400
|
+
uv run ruff check src tests
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The plugin imports only the public `led_ticker.plugin` surface — `tests/test_import_purity.py` enforces it.
|
|
404
|
+
|
|
405
|
+
## Links
|
|
406
|
+
|
|
407
|
+
- [led-ticker](https://github.com/JamesAwesome/led-ticker) — the core project
|
|
408
|
+
- [Docs site](https://docs.ledticker.dev) · [Plugin system](https://docs.ledticker.dev/plugins/)
|