diff --git a/docs/assets/basketball-scoreboard/display-modes.png b/docs/assets/basketball-scoreboard/display-modes.png new file mode 100644 index 00000000..6fe29293 Binary files /dev/null and b/docs/assets/basketball-scoreboard/display-modes.png differ diff --git a/docs/assets/basketball-scoreboard/hero.png b/docs/assets/basketball-scoreboard/hero.png new file mode 100644 index 00000000..2fa34e50 Binary files /dev/null and b/docs/assets/basketball-scoreboard/hero.png differ diff --git a/docs/assets/basketball-scoreboard/march-madness.png b/docs/assets/basketball-scoreboard/march-madness.png new file mode 100644 index 00000000..b6860f78 Binary files /dev/null and b/docs/assets/basketball-scoreboard/march-madness.png differ diff --git a/docs/assets/basketball-scoreboard/panel-sizes.png b/docs/assets/basketball-scoreboard/panel-sizes.png new file mode 100644 index 00000000..0ddb1dd7 Binary files /dev/null and b/docs/assets/basketball-scoreboard/panel-sizes.png differ diff --git a/docs/assets/basketball-scoreboard/period-states.png b/docs/assets/basketball-scoreboard/period-states.png new file mode 100644 index 00000000..e0d45e28 Binary files /dev/null and b/docs/assets/basketball-scoreboard/period-states.png differ diff --git a/docs/assets/basketball-scoreboard/shots.json b/docs/assets/basketball-scoreboard/shots.json new file mode 100644 index 00000000..eeda8d5c --- /dev/null +++ b/docs/assets/basketball-scoreboard/shots.json @@ -0,0 +1,1360 @@ +{ + "plugin": "basketball-scoreboard", + "_comment": "Basketball keeps one manager per league and mode as a plain attribute -- nba_live, ncaam_recent and so on -- and only builds them when that league's enabled flag is set. Every mode's display() returns early unless games_list is populated, and the live path also reads live_games and active_celebration. display_mode picks which of the twelve screens is drawn. The card reads home_logo_path/away_logo_path off the game dict as Path objects, and period_text is what basketball.py's extractor builds (Q1-Q4, OT1, HALF, Final, Final/OT). Tournament shots additionally set is_tournament, away_seed/home_seed, tournament_round and tournament_region, and force march_madness.tournament_mode because the plugin otherwise decides from the calendar. Teams, scores and records are invented. The tournament round and region replace the 'Next Game' header, so they draw on the UPCOMING card only; seeds draw wherever records would.", + "defaults": { + "width": 128, + "height": 32, + "scale": 6, + "freeze_time": "2026-03-21T23:30:00+00:00", + "skip_update": true, + "config": { + "enabled": true, + "nba": { + "enabled": true + }, + "wnba": { + "enabled": false + }, + "ncaam": { + "enabled": false + }, + "ncaaw": { + "enabled": false + } + } + }, + "shots": [ + { + "name": "hero", + "display_mode": "nba_live", + "attrs": { + "nba_live.games_list": [ + { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.current_game": { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + }, + "nba_live.current_game_index": 0, + "nba_live.last_game_switch": 9000000000.0, + "nba_live.last_update": 9000000000.0, + "nba_live.live_games": [ + { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.active_celebration": null + } + }, + { + "name": "m-live", + "display_mode": "nba_live", + "standalone": false, + "attrs": { + "nba_live.games_list": [ + { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.current_game": { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + }, + "nba_live.current_game_index": 0, + "nba_live.last_game_switch": 9000000000.0, + "nba_live.last_update": 9000000000.0, + "nba_live.live_games": [ + { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.active_celebration": null + } + }, + { + "name": "m-recent", + "display_mode": "nba_recent", + "standalone": false, + "attrs": { + "nba_recent.games_list": [ + { + "id": "2", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Final", + "status_text": "Final", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": false, + "is_final": true, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_recent.current_game": { + "id": "2", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Final", + "status_text": "Final", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": false, + "is_final": true, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + }, + "nba_recent.current_game_index": 0, + "nba_recent.last_game_switch": 9000000000.0, + "nba_recent.last_update": 9000000000.0 + } + }, + { + "name": "m-upcoming", + "display_mode": "nba_upcoming", + "standalone": false, + "attrs": { + "nba_upcoming.games_list": [ + { + "id": "3", + "away_abbr": "LAL", + "home_abbr": "GSW", + "away_id": "1", + "home_id": "2", + "away_score": "", + "home_score": "", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "", + "status_text": "", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": false, + "is_final": false, + "is_upcoming": true, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/LAL.png", + "home_logo_path": "assets/sports/nba_logos/GSW.png" + } + ], + "nba_upcoming.current_game": { + "id": "3", + "away_abbr": "LAL", + "home_abbr": "GSW", + "away_id": "1", + "home_id": "2", + "away_score": "", + "home_score": "", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "", + "status_text": "", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": false, + "is_final": false, + "is_upcoming": true, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/LAL.png", + "home_logo_path": "assets/sports/nba_logos/GSW.png" + }, + "nba_upcoming.current_game_index": 0, + "nba_upcoming.last_game_switch": 9000000000.0, + "nba_upcoming.last_update": 9000000000.0 + } + }, + { + "name": "m-half", + "display_mode": "nba_live", + "standalone": false, + "attrs": { + "nba_live.games_list": [ + { + "id": "4", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "52", + "home_score": "49", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "HALF", + "status_text": "HALF", + "odds": null, + "is_halftime": true, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.current_game": { + "id": "4", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "52", + "home_score": "49", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "HALF", + "status_text": "HALF", + "odds": null, + "is_halftime": true, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + }, + "nba_live.current_game_index": 0, + "nba_live.last_game_switch": 9000000000.0, + "nba_live.last_update": 9000000000.0, + "nba_live.live_games": [ + { + "id": "4", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "52", + "home_score": "49", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "HALF", + "status_text": "HALF", + "odds": null, + "is_halftime": true, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.active_celebration": null + } + }, + { + "name": "m-ot", + "display_mode": "nba_recent", + "standalone": false, + "attrs": { + "nba_recent.games_list": [ + { + "id": "5", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "101", + "home_score": "104", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Final/OT", + "status_text": "Final/OT", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": false, + "is_final": true, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_recent.current_game": { + "id": "5", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "101", + "home_score": "104", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Final/OT", + "status_text": "Final/OT", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": false, + "is_final": true, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + }, + "nba_recent.current_game_index": 0, + "nba_recent.last_game_switch": 9000000000.0, + "nba_recent.last_update": 9000000000.0 + } + }, + { + "name": "mm-seeds", + "display_mode": "ncaam_upcoming", + "standalone": false, + "attrs": { + "ncaam_upcoming.games_list": [ + { + "id": "6", + "away_abbr": "DUKE", + "home_abbr": "UNC", + "away_id": "1", + "home_id": "2", + "away_score": "", + "home_score": "", + "away_record": "27-8", + "home_record": "31-3", + "period_text": "", + "status_text": "", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": true, + "is_live": false, + "is_final": false, + "is_upcoming": true, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/ncaa_logos/DUKE.png", + "home_logo_path": "assets/sports/ncaa_logos/UNC.png", + "away_seed": 4, + "home_seed": 1, + "tournament_round": "E8", + "tournament_region": "S" + } + ], + "ncaam_upcoming.current_game": { + "id": "6", + "away_abbr": "DUKE", + "home_abbr": "UNC", + "away_id": "1", + "home_id": "2", + "away_score": "", + "home_score": "", + "away_record": "27-8", + "home_record": "31-3", + "period_text": "", + "status_text": "", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": true, + "is_live": false, + "is_final": false, + "is_upcoming": true, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/ncaa_logos/DUKE.png", + "home_logo_path": "assets/sports/ncaa_logos/UNC.png", + "away_seed": 4, + "home_seed": 1, + "tournament_round": "E8", + "tournament_region": "S" + }, + "ncaam_upcoming.current_game_index": 0, + "ncaam_upcoming.last_game_switch": 9000000000.0, + "ncaam_upcoming.last_update": 9000000000.0 + }, + "config": { + "enabled": true, + "nba": { + "enabled": false + }, + "wnba": { + "enabled": false + }, + "ncaam": { + "enabled": true, + "march_madness": { + "tournament_mode": true, + "show_seeds": true, + "show_round": true, + "show_region": false + }, + "display_options": { + "show_ranking": false, + "show_records": false + } + }, + "ncaaw": { + "enabled": false + } + } + }, + { + "name": "mm-region", + "display_mode": "ncaam_upcoming", + "standalone": false, + "attrs": { + "ncaam_upcoming.games_list": [ + { + "id": "6", + "away_abbr": "DUKE", + "home_abbr": "UNC", + "away_id": "1", + "home_id": "2", + "away_score": "", + "home_score": "", + "away_record": "27-8", + "home_record": "31-3", + "period_text": "", + "status_text": "", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": true, + "is_live": false, + "is_final": false, + "is_upcoming": true, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/ncaa_logos/DUKE.png", + "home_logo_path": "assets/sports/ncaa_logos/UNC.png", + "away_seed": 4, + "home_seed": 1, + "tournament_round": "E8", + "tournament_region": "S" + } + ], + "ncaam_upcoming.current_game": { + "id": "6", + "away_abbr": "DUKE", + "home_abbr": "UNC", + "away_id": "1", + "home_id": "2", + "away_score": "", + "home_score": "", + "away_record": "27-8", + "home_record": "31-3", + "period_text": "", + "status_text": "", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": true, + "is_live": false, + "is_final": false, + "is_upcoming": true, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/ncaa_logos/DUKE.png", + "home_logo_path": "assets/sports/ncaa_logos/UNC.png", + "away_seed": 4, + "home_seed": 1, + "tournament_round": "E8", + "tournament_region": "S" + }, + "ncaam_upcoming.current_game_index": 0, + "ncaam_upcoming.last_game_switch": 9000000000.0, + "ncaam_upcoming.last_update": 9000000000.0 + }, + "config": { + "enabled": true, + "nba": { + "enabled": false + }, + "wnba": { + "enabled": false + }, + "ncaam": { + "enabled": true, + "march_madness": { + "tournament_mode": true, + "show_seeds": true, + "show_round": true, + "show_region": true + }, + "display_options": { + "show_ranking": false, + "show_records": false + } + }, + "ncaaw": { + "enabled": false + } + } + }, + { + "name": "mm-off", + "display_mode": "ncaam_upcoming", + "standalone": false, + "attrs": { + "ncaam_upcoming.games_list": [ + { + "id": "6", + "away_abbr": "DUKE", + "home_abbr": "UNC", + "away_id": "1", + "home_id": "2", + "away_score": "", + "home_score": "", + "away_record": "27-8", + "home_record": "31-3", + "period_text": "", + "status_text": "", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": true, + "is_live": false, + "is_final": false, + "is_upcoming": true, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/ncaa_logos/DUKE.png", + "home_logo_path": "assets/sports/ncaa_logos/UNC.png", + "away_seed": 4, + "home_seed": 1, + "tournament_round": "E8", + "tournament_region": "S" + } + ], + "ncaam_upcoming.current_game": { + "id": "6", + "away_abbr": "DUKE", + "home_abbr": "UNC", + "away_id": "1", + "home_id": "2", + "away_score": "", + "home_score": "", + "away_record": "27-8", + "home_record": "31-3", + "period_text": "", + "status_text": "", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": true, + "is_live": false, + "is_final": false, + "is_upcoming": true, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/ncaa_logos/DUKE.png", + "home_logo_path": "assets/sports/ncaa_logos/UNC.png", + "away_seed": 4, + "home_seed": 1, + "tournament_round": "E8", + "tournament_region": "S" + }, + "ncaam_upcoming.current_game_index": 0, + "ncaam_upcoming.last_game_switch": 9000000000.0, + "ncaam_upcoming.last_update": 9000000000.0 + }, + "config": { + "enabled": true, + "nba": { + "enabled": false + }, + "wnba": { + "enabled": false + }, + "ncaam": { + "enabled": true, + "march_madness": { + "tournament_mode": true, + "show_seeds": false, + "show_round": false, + "show_region": false + }, + "display_options": { + "show_ranking": false, + "show_records": false + } + }, + "ncaaw": { + "enabled": false + } + } + }, + { + "name": "rec-on", + "display_mode": "nba_upcoming", + "standalone": false, + "attrs": { + "nba_upcoming.games_list": [ + { + "id": "3", + "away_abbr": "LAL", + "home_abbr": "GSW", + "away_id": "1", + "home_id": "2", + "away_score": "", + "home_score": "", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "", + "status_text": "", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": false, + "is_final": false, + "is_upcoming": true, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/LAL.png", + "home_logo_path": "assets/sports/nba_logos/GSW.png" + } + ], + "nba_upcoming.current_game": { + "id": "3", + "away_abbr": "LAL", + "home_abbr": "GSW", + "away_id": "1", + "home_id": "2", + "away_score": "", + "home_score": "", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "", + "status_text": "", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": false, + "is_final": false, + "is_upcoming": true, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/LAL.png", + "home_logo_path": "assets/sports/nba_logos/GSW.png" + }, + "nba_upcoming.current_game_index": 0, + "nba_upcoming.last_game_switch": 9000000000.0, + "nba_upcoming.last_update": 9000000000.0 + }, + "config": { + "enabled": true, + "nba": { + "enabled": true, + "display_options": { + "show_records": true + } + }, + "wnba": { + "enabled": false + }, + "ncaam": { + "enabled": false + }, + "ncaaw": { + "enabled": false + } + } + }, + { + "name": "rec-off", + "display_mode": "nba_upcoming", + "standalone": false, + "attrs": { + "nba_upcoming.games_list": [ + { + "id": "3", + "away_abbr": "LAL", + "home_abbr": "GSW", + "away_id": "1", + "home_id": "2", + "away_score": "", + "home_score": "", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "", + "status_text": "", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": false, + "is_final": false, + "is_upcoming": true, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/LAL.png", + "home_logo_path": "assets/sports/nba_logos/GSW.png" + } + ], + "nba_upcoming.current_game": { + "id": "3", + "away_abbr": "LAL", + "home_abbr": "GSW", + "away_id": "1", + "home_id": "2", + "away_score": "", + "home_score": "", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "", + "status_text": "", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": false, + "is_final": false, + "is_upcoming": true, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/LAL.png", + "home_logo_path": "assets/sports/nba_logos/GSW.png" + }, + "nba_upcoming.current_game_index": 0, + "nba_upcoming.last_game_switch": 9000000000.0, + "nba_upcoming.last_update": 9000000000.0 + }, + "config": { + "enabled": true, + "nba": { + "enabled": true, + "display_options": { + "show_records": false + } + }, + "wnba": { + "enabled": false + }, + "ncaam": { + "enabled": false + }, + "ncaaw": { + "enabled": false + } + } + }, + { + "name": "size-64x32", + "width": 64, + "height": 32, + "display_mode": "nba_live", + "standalone": false, + "attrs": { + "nba_live.games_list": [ + { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.current_game": { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + }, + "nba_live.current_game_index": 0, + "nba_live.last_game_switch": 9000000000.0, + "nba_live.last_update": 9000000000.0, + "nba_live.live_games": [ + { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.active_celebration": null + } + }, + { + "name": "size-128x32", + "width": 128, + "height": 32, + "display_mode": "nba_live", + "standalone": false, + "attrs": { + "nba_live.games_list": [ + { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.current_game": { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + }, + "nba_live.current_game_index": 0, + "nba_live.last_game_switch": 9000000000.0, + "nba_live.last_update": 9000000000.0, + "nba_live.live_games": [ + { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.active_celebration": null + } + }, + { + "name": "size-128x64", + "width": 128, + "height": 64, + "display_mode": "nba_live", + "standalone": false, + "attrs": { + "nba_live.games_list": [ + { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.current_game": { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + }, + "nba_live.current_game_index": 0, + "nba_live.last_game_switch": 9000000000.0, + "nba_live.last_update": 9000000000.0, + "nba_live.live_games": [ + { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.active_celebration": null + } + }, + { + "name": "size-256x32", + "width": 256, + "height": 32, + "display_mode": "nba_live", + "standalone": false, + "attrs": { + "nba_live.games_list": [ + { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.current_game": { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + }, + "nba_live.current_game_index": 0, + "nba_live.last_game_switch": 9000000000.0, + "nba_live.last_update": 9000000000.0, + "nba_live.live_games": [ + { + "id": "1", + "away_abbr": "BOS", + "home_abbr": "DEN", + "away_id": "1", + "home_id": "2", + "away_score": "88", + "home_score": "94", + "away_record": "41-22", + "home_record": "48-15", + "period_text": "Q3", + "status_text": "LIVE", + "odds": null, + "is_halftime": false, + "is_period_break": false, + "is_tournament": false, + "is_live": true, + "is_final": false, + "is_upcoming": false, + "game_date": "3/21", + "game_time": "7:30pm", + "away_logo_path": "assets/sports/nba_logos/BOS.png", + "home_logo_path": "assets/sports/nba_logos/DEN.png" + } + ], + "nba_live.active_celebration": null + } + } + ], + "composites": [ + { + "name": "display-modes", + "columns": 1, + "cells": [ + { + "shot": "m-live", + "label": "nba_live", + "sublabel": "quarter and the running score" + }, + { + "shot": "m-recent", + "label": "nba_recent", + "sublabel": "the finished game" + }, + { + "shot": "m-upcoming", + "label": "nba_upcoming", + "sublabel": "tip-off date and time" + } + ] + }, + { + "name": "period-states", + "columns": 1, + "cells": [ + { + "shot": "m-live", + "label": "Q3", + "sublabel": "a quarter in progress; OT1 past the fourth" + }, + { + "shot": "m-half", + "label": "HALF", + "sublabel": "half-time" + }, + { + "shot": "m-ot", + "label": "Final/OT", + "sublabel": "decided in overtime" + } + ] + }, + { + "name": "march-madness", + "columns": 1, + "cells": [ + { + "shot": "mm-seeds", + "label": "seeds and round", + "sublabel": "show_seeds and show_round, both on by default" + }, + { + "shot": "mm-region", + "label": "plus the region", + "sublabel": "show_region appends the region, off by default" + }, + { + "shot": "mm-off", + "label": "all three off", + "sublabel": "all three off: the ordinary Next Game header" + } + ] + }, + { + "name": "show-records", + "columns": 1, + "cells": [ + { + "shot": "rec-on", + "label": "show_records: true" + }, + { + "shot": "rec-off", + "label": "show_records: false" + } + ] + }, + { + "name": "panel-sizes", + "columns": 2, + "cells": [ + { + "shot": "size-64x32", + "label": "64 x 32" + }, + { + "shot": "size-128x32", + "label": "128 x 32" + }, + { + "shot": "size-128x64", + "label": "128 x 64" + }, + { + "shot": "size-256x32", + "label": "256 x 32" + } + ] + } + ] +} diff --git a/docs/assets/basketball-scoreboard/show-records.png b/docs/assets/basketball-scoreboard/show-records.png new file mode 100644 index 00000000..d249a46f Binary files /dev/null and b/docs/assets/basketball-scoreboard/show-records.png differ diff --git a/plugins/basketball-scoreboard/README.md b/plugins/basketball-scoreboard/README.md index 4686110a..9bc64cf2 100644 --- a/plugins/basketball-scoreboard/README.md +++ b/plugins/basketball-scoreboard/README.md @@ -1,398 +1,499 @@ ------------------------------------------------------------------------------------ ### Connect with ChuckBuilds -- Show support on Youtube: https://www.youtube.com/@ChuckBuilds +- Show support on YouTube: https://www.youtube.com/@ChuckBuilds - Stay in touch on Instagram: https://www.instagram.com/ChuckBuilds/ - Want to chat or need support? Reach out on the ChuckBuilds Discord: https://discord.com/invite/uW36dVAtcT -- Feeling Generous? Support the project: - - Github Sponsorship: https://github.com/sponsors/ChuckBuilds +- Feeling generous? Support the project: + - GitHub Sponsors: https://github.com/sponsors/ChuckBuilds - Buy Me a Coffee: https://buymeacoffee.com/chuckbuilds - - Ko-fi: https://ko-fi.com/chuckbuilds/ + - Ko-fi: https://ko-fi.com/chuckbuilds/ ------------------------------------------------------------------------------------ +--- -# Basketball Scoreboard Plugin +# Basketball Scoreboard -A plugin for LEDMatrix that displays live, recent, and upcoming basketball games across NBA, NCAA Men's Basketball, NCAA Women's Basketball, and WNBA leagues. +Live, recent, and upcoming games across the **NBA**, **WNBA**, **NCAA Men's**, +and **NCAA Women's** basketball on your LEDMatrix display, from ESPN's public +API. No API key required. -## Features +![NBA live scorebug](../../docs/assets/basketball-scoreboard/hero.png) -- **Multiple League Support**: NBA, NCAA Men's Basketball, NCAA Women's Basketball, WNBA -- **Live Game Tracking**: Real-time scores, quarters, time remaining -- **Recent Games**: Recently completed games with final scores -- **Upcoming Games**: Scheduled games with start times -- **Favorite Teams**: Prioritize games involving your favorite teams -- **Live Priority Mode**: Live games can interrupt normal rotation when enabled -- **Background Data Fetching**: Efficient API calls without blocking display -- **Per-League Configuration**: Independent settings for each league -- **Flexible Display Options**: Show records, rankings, and betting odds -- **Advanced Filtering**: Control which teams and games are displayed -- **Favorite Team Result Colors**: Optionally show a finished game's score in green when your favorite team won and red when it lost +## Contents -## Configuration +- [Quick start](#quick-start) +- [Display modes](#display-modes) +- [The four leagues](#the-four-leagues) +- [March Madness](#march-madness) +- [How games are chosen](#how-games-are-chosen) +- [NCAA season data](#ncaa-season-data) +- [Panel sizes](#panel-sizes) +- [Settings reference](#settings-reference) +- [Per-league settings](#per-league-settings) +- [Matchup separator and the upcoming card middle](#matchup-separator-and-the-upcoming-card-middle) +- [Fonts, colours and layout](#fonts-colours-and-layout) +- [Favorite team result colours](#favorite-team-result-colours) +- [Vegas ticker: seeing live games more often](#vegas-ticker-seeing-live-games-more-often) +- [Team abbreviations](#team-abbreviations) +- [Installation](#installation) +- [Troubleshooting](#troubleshooting) -### Global Settings +## Quick start -- `display_duration`: How long the plugin mode is shown before rotating to next plugin (5-300 seconds, default: 30) -- `update_interval`: How often to fetch new data in seconds (30-86400, default: 3600) -- `game_display_duration`: Duration to show each individual game before rotating to next game (3-60 seconds, default: 15) -- `background_service`: Configure API request settings (timeout, retries, priority) -- `timezone` (Advanced): IANA name used to display event start times, e.g. - `America/Chicago`. Leave blank (the default) to follow the LEDMatrix global - timezone; if that isn't set, the host system's timezone is used, and only if - neither is available do times fall back to UTC. - -### Per-League Settings - -#### NBA Configuration +1. Install **Basketball Scoreboard** from the LEDMatrix Plugin Store. +2. Turn on `enabled`, then the leagues you want — `nba` is on by default; WNBA + and both NCAA leagues are off. +3. Add your teams under each league's **Favorite Teams**. ```json { - "nba": { + "basketball-scoreboard": { "enabled": true, - "favorite_teams": ["LAL", "BOS", "GSW"], - "display_modes": { - "show_live": true, - "show_recent": true, - "show_upcoming": true - }, - "live_priority": true, - "live_game_duration": 20, - "live_update_interval": 30, - "update_interval_seconds": 3600, - "game_limits": { - "recent_games_to_show": 1, - "upcoming_games_to_show": 1 - }, - "display_options": { - "show_records": false, - "show_ranking": false, - "show_odds": true - }, - "filtering": { - "show_favorite_teams_only": true, - "show_all_live": false + "nba": { + "enabled": true, + "favorite_teams": ["BOS", "DEN"], + "filtering": { "show_favorite_teams_only": false }, + "game_limits": { + "recent_games_to_show": 2, + "upcoming_games_to_show": 3 + } }, - "display_durations": { - "base": 15, - "live": 20, - "recent": 15, - "upcoming": 15 + "ncaam": { + "enabled": true, + "favorite_teams": ["DUKE", "UNC"] } } } ``` -**Configuration Options:** - -- `enabled`: Enable/disable NBA games (default: true) -- `favorite_teams`: Array of team abbreviations (e.g., ["LAL", "BOS", "GSW"]) -- `display_modes`: Control which game types to show - - `show_live`: Show live games (default: true) - - `show_recent`: Show recently completed games (default: true) - - `show_upcoming`: Show upcoming games (default: true) -- `live_priority`: Give live games priority over other modes - interrupts normal rotation (default: true) -- `live_game_duration`: Duration in seconds to display each live game (10-120, default: 20). With a non-favorite duration set, this applies to games with a favorite team. -- `non_favorite_live_game_duration`: Duration in seconds for live games with **no** favorite team (0-120, default: 0 = off). Only applies when favorite teams are set and non-favorite live games are shown (`show_favorite_teams_only` off, or `show_all_live` on). See [Shorter dwell for non-favorite live games](#shorter-dwell-for-non-favorite-live-games). -- `live_update_interval`: How often to update live game data in seconds (5-300, default: 30) -- `update_interval_seconds`: How often to fetch new data in seconds (30-86400, default: 3600) -- `game_limits`: Control how many games to show - - `recent_games_to_show`: With favorite teams: per team (e.g., 1 with 2 teams = 2 games). Without favorites: total games (default: 1) - - `upcoming_games_to_show`: With favorite teams: per team (e.g., 2 with 3 teams = up to 6 games). Without favorites: total games (default: 1) -- `display_options`: Additional information to show - - `show_records`: Show team win-loss records (default: false) - - `show_ranking`: Show team rankings when available (default: false) - - `show_odds`: Show betting odds (default: true) -- `filtering`: Control which teams are shown - - `show_favorite_teams_only`: Only show games from favorite teams (default: true) - - `show_all_live`: Show all live games, not just favorites (default: false) -- `display_durations`: Per-mode display durations in seconds (5-120) - - `base`: Base duration (default: 15) - - `live`: Live games duration (default: 20) - - `recent`: Recent games duration (default: 15) - - `upcoming`: Upcoming games duration (default: 15) - -#### NCAA Men's Basketball Configuration - -**Note**: Full season data is only fetched for teams in `favorite_teams`. Recent/Upcoming modes require favorite teams to be configured. +## Display modes -```json -{ - "ncaam": { - "enabled": true, - "favorite_teams": ["DUKE", "UNC", "KANSAS"], - "display_modes": { - "show_live": true, - "show_recent": true, - "show_upcoming": true - }, - "live_priority": true, - "live_game_duration": 20, - "live_update_interval": 30, - "update_interval_seconds": 3600, - "game_limits": { - "recent_games_to_show": 1, - "upcoming_games_to_show": 1 - }, - "display_options": { - "show_records": false, - "show_ranking": false, - "show_odds": true - }, - "filtering": { - "show_favorite_teams_only": true, - "show_all_live": false - } - } -} -``` +Twelve modes — three per league — that the LEDMatrix host rotation cycles +through independently. -**Configuration Options:** Same as NBA (see NBA Configuration section above for detailed descriptions). +![The three NBA display modes](../../docs/assets/basketball-scoreboard/display-modes.png) -#### NCAA Women's Basketball Configuration +| Mode | Shows | Top line | +|---|---|---| +| `nba_live` | NBA games in progress | `Q1`–`Q4`, `OT1` past the fourth, or `HALF` | +| `nba_recent` | Finished NBA games | `Final`, or `Final/OT` | +| `nba_upcoming` | Scheduled NBA games | `Next Game`, then the date and tip-off | +| `wnba_*`, `ncaam_*`, `ncaaw_*` | The same three per league | As above | -**Note**: Full season data is only fetched for teams in `favorite_teams`. Recent/Upcoming modes require favorite teams to be configured. +The three period states: -```json -{ - "ncaaw": { - "enabled": true, - "favorite_teams": ["UCONN", "SCAR", "STAN"], - "display_modes": { - "show_live": true, - "show_recent": true, - "show_upcoming": true - }, - "live_priority": true, - "live_game_duration": 20, - "live_update_interval": 30, - "update_interval_seconds": 3600, - "game_limits": { - "recent_games_to_show": 1, - "upcoming_games_to_show": 1 - }, - "display_options": { - "show_records": false, - "show_ranking": false, - "show_odds": true - }, - "filtering": { - "show_favorite_teams_only": true, - "show_all_live": false - } - } -} -``` +![Q3, HALF and Final/OT](../../docs/assets/basketball-scoreboard/period-states.png) -**Configuration Options:** Same as NBA (see NBA Configuration section above for detailed descriptions). +Each mode renders as **switch** (one game at a time, timed) or **scroll** (all +games scroll horizontally at high FPS), set per league and per mode with +`.display_modes._display_mode`. -#### WNBA Configuration +> **The mode toggles are named `show_live`, `show_recent`, `show_upcoming`**, +> not `live` / `recent` / `upcoming` as in the hockey and lacrosse scoreboards. +> A `display_modes` block copied from one of those silently leaves every mode at +> its default. -```json -{ - "wnba": { - "enabled": true, - "favorite_teams": ["LVA", "NYL", "CHI"], - "display_modes": { - "show_live": true, - "show_recent": true, - "show_upcoming": true - }, - "live_priority": true, - "live_game_duration": 20, - "live_update_interval": 30, - "update_interval_seconds": 3600, - "game_limits": { - "recent_games_to_show": 1, - "upcoming_games_to_show": 1 - }, - "display_options": { - "show_records": false, - "show_ranking": false, - "show_odds": true - }, - "filtering": { - "show_favorite_teams_only": true, - "show_all_live": false - } - } -} -``` +The rotation follows the order in `manifest.json` — `nba_recent`, +`nba_upcoming`, `nba_live`, then the same three for `wnba`, `ncaam`, and +`ncaaw`. Reorder them there to change the sequence. A league or mode disabled in +the config makes the plugin return `False` for that mode and the controller +skips it, so the rotation closes up around it. -**Configuration Options:** Same as NBA (see NBA Configuration section above for detailed descriptions). +## The four leagues -## Display Modes +Each league has its own managers, its own favorites, and its own config block. +The blocks are near-identical; only two defaults differ, plus one extra section: -The plugin registers granular display modes per league. Each league has three modes: +| Key | NBA | WNBA | NCAA M | NCAA W | +|---|---|---|---|---| +| `.enabled` | `true` | `false` | `false` | `false` | +| `.display_options.show_ranking` | `false` | `false` | `true` | `true` | -### NBA Modes -- **nba_live**: Shows currently active NBA games -- **nba_recent**: Shows recently completed NBA games -- **nba_upcoming**: Shows scheduled upcoming NBA games +Rankings default on for the college leagues because, as the schema puts it, +they matter a great deal there. -### WNBA Modes -- **wnba_live**: Shows currently active WNBA games -- **wnba_recent**: Shows recently completed WNBA games -- **wnba_upcoming**: Shows scheduled upcoming WNBA games +**The two NCAA blocks additionally carry a `march_madness` section** that the +NBA and WNBA blocks do not have. Everything else under +[Per-league settings](#per-league-settings) is identical across all four, with +the prefix `nba.`, `wnba.`, `ncaam.`, or `ncaaw.`. -### NCAA Men's Basketball Modes -- **ncaam_live**: Shows currently active NCAA Men's games -- **ncaam_recent**: Shows recently completed NCAA Men's games -- **ncaam_upcoming**: Shows scheduled upcoming NCAA Men's games +## March Madness -### NCAA Women's Basketball Modes -- **ncaaw_live**: Shows currently active NCAA Women's games -- **ncaaw_recent**: Shows recently completed NCAA Women's games -- **ncaaw_upcoming**: Shows scheduled upcoming NCAA Women's games +During the NCAA tournament the college cards change: seeds replace AP rankings, +and the round — optionally with the bracket region — replaces the "Next Game" +header. -### Live Priority Mode +![Seeds, round and region](../../docs/assets/basketball-scoreboard/march-madness.png) -When `live_priority` is enabled for a league, live games will: -- Interrupt the normal mode rotation -- Be displayed immediately when available -- Take priority over other plugin modes -- Only show if there are actual live games available +| Key | Type | Default | What it does | +|---|---|---|---| +| `.march_madness.tournament_mode` | boolean | unset | Show **all** tournament games regardless of favorite teams. Left unset, the plugin turns this on automatically during the tournament window and off outside it. | +| `.march_madness.show_seeds` | boolean | `true` | Draw tournament seeds (1–16) in place of AP rankings for tournament games. | +| `.march_madness.show_round` | boolean | `true` | Draw the round abbreviation — `R64`, `R32`, `S16`, `E8`, `F4`, `NCG` — in the status area. | +| `.march_madness.show_region` | boolean | `false` | Append the bracket region (`E`, `W`, `S`, `MW`) after the round. | +| `.march_madness.tournament_games_limit` | 1–32 | `10` | Cap on non-favorite tournament games in the Recent and Upcoming pools. | -This feature allows you to never miss live action - when a game goes live, it will automatically be shown on the display, even if other content was scheduled. +All five are **Advanced**, and all five exist only under `ncaam` and `ncaaw`. -## Team Abbreviations +The round and region replace the **"Next Game"** header, so they appear on the +Upcoming card; seeds are drawn wherever records would be, on any tournament +card. The label text comes from the feed — the abbreviations above are what +ESPN publishes. -### NBA Teams -Common abbreviations: LAL, BOS, GSW, MIL, PHI, DEN, MIA, BKN, ATL, CHA, NYK, IND, DET, TOR, CHI, CLE, ORL, WAS, HOU, SAS, MIN, POR, SAC, LAC, MEM, DAL, PHX, UTA, OKC, NOP +## How games are chosen -### NCAA Men's Basketball Teams -Common abbreviations: DUKE, UNC, KANSAS, KENTUCKY, UCLA, ARIZONA, GONZAGA, BAYLOR, VILLANOVA, MICHIGAN, OHIOST, FLORIDA, WISCONSIN, MARYLAND, VIRGINIA, LOUISVILLE, SYRACUSE, INDIANA, PURDUE, IOWA +**`upcoming_games_to_show` is not "how many cards you see".** It is the size of +a *pool*. The panel cycles through that pool one card at a time and keeps its +place between visits, so a pool of 3 means the board rotates through the same 3 +games until the schedule moves on. A bigger number gives you a *longer lap*, so +any one game comes round **less** often. -### NCAA Women's Basketball Teams -Common abbreviations: UCONN, SCAR (South Carolina), STAN (Stanford), BAYLOR, LOUISVILLE, OREGON, MISSST (Mississippi State), NDAME (Notre Dame), DUKE, MARYLAND, UCLA, ARIZONA, OREGONST (Oregon State), FLORIDA, TENNESSEE, TEXAS, OKLAHOMA, IOWA +Which regime you are in depends on that league's `favorite_teams` and +`filtering.show_favorite_teams_only`: -### WNBA Teams -Common abbreviations: LVA (Las Vegas Aces), NYL (New York Liberty), CHI (Chicago Sky), CONN (Connecticut Sun), DAL (Dallas Wings), ATL (Atlanta Dream), IND (Indiana Fever), MIN (Minnesota Lynx), PHX (Phoenix Mercury), SEA (Seattle Storm), WAS (Washington Mystics), LAC (Los Angeles Sparks) +| `favorite_teams` | `show_favorite_teams_only` | What you get | +|---|---|---| +| empty | either | The next N games league-wide, chronologically. Every game is a non-favorite game, so the `other_*` filters apply to all of them. | +| set | **on** (default) | Only your teams. The limit is a budget **per team** — `2` with three favorites is up to six games. | +| set | **off** | **Your teams first, then other games to fill.** Both limits are **totals**. | -## Background Service +### The selection settings -The plugin uses background data fetching for efficient API calls: +Per league, under `game_limits`, all **Advanced**: -- Requests timeout after 30 seconds (configurable via `background_service.request_timeout`) -- Up to 3 retries for failed requests (configurable via `background_service.max_retries`) -- Priority level 2 (medium priority, configurable via `background_service.priority`) +| Option | Default | Description | +|---|---|---| +| `recent_games_to_show` | `1` | Pool size for finished games. | +| `upcoming_games_to_show` | `1` | The same for scheduled games. | +| `other_recent_games_to_show` | `1` | How many **non-favorite** finished games to add. `0` gives favorites only. | +| `other_upcoming_games_to_show` | `1` | The same for scheduled games. | +| `other_rotation_interval_seconds` | `1800` | How often the non-favorite slice advances. `0` pins it. | +| `other_games_min_quality` | `ranked` | Which non-favorite games qualify: `any` or `ranked`. | +| `other_games_divisions` | `["fbs"]` | Which divisions non-favorite games may come from. | -Configure in `background_service`: -```json -{ - "background_service": { - "request_timeout": 30, - "max_retries": 3, - "priority": 2 - } -} -``` +**Your favorite teams are never filtered by the last two.** Those settings only +decide what fills the *remaining* slots. -## Data Source +> `other_games_min_quality` works in the two **college** leagues, which have a +> national poll to rank against; in the NBA and WNBA there is no poll, so every +> game passes and the setting costs nothing. `other_games_divisions` is inert in +> all four — FBS/FCS is a college *football* taxonomy and no lookup is +> attempted. The schema's help text for `other_games_min_quality` also mentions +> a `broadcast` option the enum does not offer; it was retired. -Game data is fetched from ESPN's public API endpoints for all supported basketball leagues. +Within the other-games pool the better matchup leads and each team appears once, +ordered by the best poll position of either side with ties falling back to +tip-off order. A league with no poll keeps chronological order. Your favorite +teams are ordered by when they play, not by rank. -### NCAA Basketball Season Data +### Variety comes from turnover -**Important**: For NCAA Men's and Women's Basketball, full season data is only fetched for teams in your `favorite_teams` list: +Rather than widening the pool, the non-favorite slice **moves**: the window +advances by its own width every `other_rotation_interval_seconds`, so +consecutive windows do not overlap and the board works through the schedule +instead of resampling the front of it. Your favorites are not rotated. -- **Live Mode**: Shows all current/live games (not limited to favorite teams) -- **Recent/Upcoming Modes**: Only displays games from your favorite teams' full season schedules -- **No Favorite Teams**: If no favorite teams are configured, Recent/Upcoming modes will only show games from the current scoreboard (limited data) +Both filters **fail open**: if the data behind them cannot be fetched, the game +is allowed through. They fail open a second time as a set — if the filters +between them leave nothing at all, the unfiltered list is used instead. Setting +`other_upcoming_games_to_show` or `other_recent_games_to_show` to `0` is the one +way to ask for an empty slate, and that is honoured. -This approach works around ESPN API limitations that prevent fetching full season schedules via date ranges for college basketball. The plugin uses team-specific schedule endpoints (`/teams/{id}/schedule`) to get complete season data for each favorite team. +### Live rotation -**NBA and WNBA**: These leagues support date range queries, so full season data is available regardless of favorite teams configuration. +When several games are live at once the rotation is weighted: a game involving +one of your teams gets `filtering.favorite_live_boost` turns for every one turn +other live games get, and is queued first whenever the rotation refreshes. It +never interrupts a game already on screen. Set it to `1` for even rotation, and +note it is independent of `live_priority`, which controls whether live games +preempt the recent/upcoming rotation at all. -## Dependencies +A live game the API stops reporting for `stale_game_timeout` seconds is dropped, +so an abandoned game does not sit on the board forever. -This plugin requires the main LEDMatrix installation and inherits functionality from the Basketball base classes. +### Shorter dwell for non-favorite live games -## Installation +`non_favorite_live_game_duration` (0–120, default `0` = off) gives live games +involving **none** of your teams a shorter turn. It only takes effect when +favorite teams are configured **and** non-favorite live games are being shown — +`filtering.show_favorite_teams_only` off, or `filtering.show_all_live` on: -The easiest way is the Plugin Store in the LEDMatrix web UI: +| Favorites set? | Non-favorite games shown? | Game has a favorite? | Duration used | +|---|---|---|---| +| No | — | — | `live_game_duration` | +| Yes | No | favorite | `live_game_duration` | +| Yes | Yes | favorite | `live_game_duration` | +| Yes | Yes | none | `non_favorite_live_game_duration`, when above `0` | -1. Open `http://your-pi-ip:5000` -2. Open the **Plugin Manager** tab -3. Find **Basketball Scoreboard** in the **Plugin Store** section and - click **Install** -4. Open the plugin's tab in the second nav row to configure favorite - teams and per-league preferences +### Excluding teams -Manual install: copy this directory into your LEDMatrix -`plugins_directory` (default `plugin-repos/`) and restart the display -service. +`exclude_teams` hides teams from **both** the live rotation and the recent/final +scores — useful when you plan to watch a game delayed. It uses the same +abbreviations as `favorite_teams` and always wins when a team appears in both +lists. -## Game Limits Behavior +## NCAA season data -The `game_limits` configuration behaves differently based on whether favorite teams are configured: +**For the two college leagues, full season data is only fetched for teams in +`favorite_teams`.** ESPN does not support date-range queries for college +basketball schedules, so the plugin uses the per-team endpoint +(`/teams/{id}/schedule`) for each favorite instead. -### With Favorite Teams -- `recent_games_to_show`: Number of recent games **per team** - - Example: `1` with 2 favorite teams = up to 2 games total (1 per team) - - Example: `2` with 3 favorite teams = up to 6 games total (2 per team) -- `upcoming_games_to_show`: Number of upcoming games **per team** - - Example: `1` with 2 favorite teams = up to 2 games total (1 per team) - - Example: `3` with 2 favorite teams = up to 6 games total (3 per team) +| Mode | With favorites | Without favorites | +|---|---|---| +| Live | All current games | All current games | +| Recent / Upcoming | Each favorite's full season | Only what the current scoreboard returns, which is a narrow window | -### Without Favorite Teams -- `recent_games_to_show`: Total number of most recent games to show - - Example: `5` = show the 5 most recent games total -- `upcoming_games_to_show`: Total number of next upcoming games to show - - Example: `1` = show only the next 1 game total +**The NBA and WNBA are unaffected** — both support date-range queries, so full +season data is available whether or not favorites are set. -## Filtering Options +## Panel sizes -The `filtering` section controls which games are displayed: +![Live card at four panel sizes](../../docs/assets/basketball-scoreboard/panel-sizes.png) -- `show_favorite_teams_only` (default: true): When enabled, only shows games involving your favorite teams. When disabled, shows all games. -- `show_all_live` (default: false): When enabled, shows all live games regardless of favorite teams setting. This is useful if you want to see all live action even if you only have favorite teams configured for recent/upcoming modes. -- `favorite_live_boost` (default: 2, range 1-5): With both filters above off (or `show_all_live` on), all live games rotate evenly by default. This setting gives your favorite's live game extra turns in that rotation — it's always queued first whenever the live list refreshes, and gets `favorite_live_boost` turns for every 1 turn other live games get. Set to `1` for perfectly even rotation (no boost). Has no effect if you don't have `favorite_teams` configured, or if your favorite isn't currently live. +The plugin passes the render-safety harness on all eight supported sizes. At +64x32 the two crests and the centre column share very little room; 128x32 or +wider is a much better fit. -**Note**: For live mode, if `show_all_live` is true, all live games will be shown. If false and `show_favorite_teams_only` is true, only live games involving favorite teams will be shown. If both are off, all live games are shown and rotate evenly, with `favorite_live_boost` giving your favorite's game precedence whenever it's playing. +## Settings reference -### Shorter dwell for non-favorite live games +Settings marked **Advanced** sit behind the *Advanced* toggle in the web UI. +Defaults are the schema defaults, which is what the web UI writes. + +### Plugin level + +| Key | Type | Default | What it does | +|---|---|---|---| +| `enabled` | boolean | `true` | Master on/off switch for the whole plugin. | +| `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` | **Advanced.** Per-game time within a mode, where the league does not override it. | +| `update_interval` | 30–86400 s | `3600` | **Advanced.** Base data refresh cadence. | +| `timezone` | string | `""` | **Advanced.** IANA zone for tip-off 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 screens. | +| `schedule_lookahead_days` | 1–60 | `7` | **Advanced.** How far ahead to fetch for Upcoming. A game beyond this horizon is never fetched. | +| `no_data_interval_seconds` | 5–86400 s | `300` | **Advanced.** Wait between live checks when nothing is live. Backs off further the longer nothing is found. | +| `live_idle_max_interval_seconds` | 5–86400 s | `900` | **Advanced.** Ceiling for that back-off. Useful out of season. | + +### Background service + +All **Advanced**; the defaults suit a Pi and rarely want changing. + +| Key | Type | Default | What it does | +|---|---|---|---| +| `background_service.request_timeout` | 1–300 s | `30` | API call timeout. | +| `background_service.max_retries` | 0–10 | `3` | Retries for a failed request. | +| `background_service.priority` | 1–5 | `2` | Request priority; 1 is highest. | + +## Per-league settings + +Every table below exists four times — under `nba`, `wnba`, `ncaam`, and +`ncaaw` — with the same keys and defaults, except the two noted in +[The four leagues](#the-four-leagues) and the `march_madness` block, which the +NBA and WNBA do not have. `` stands for any of them. + +### Teams and priority + +| Key | Type | Default | What it does | +|---|---|---|---| +| `.enabled` | boolean | `true` for `nba`, `false` for the rest | Build this league's managers at all. | +| `.favorite_teams` | array | `[]` | Teams to prioritise, by abbreviation. | +| `.exclude_teams` | array | `[]` | **Advanced.** Teams to always hide, from the live rotation and from finals alike. Takes precedence over `favorite_teams` and `show_all_live`. | +| `.live_priority` | boolean | `true` | Let this league's live games interrupt the rotation and display immediately. | + +### Display modes + +| Key | Type | Default | +|---|---|---| +| `.display_modes.show_live` | boolean | `true` | +| `.display_modes.show_recent` | boolean | `true` | +| `.display_modes.show_upcoming` | boolean | `true` | +| `.display_modes.live_display_mode` | `switch` \| `scroll` | `switch` (**Advanced**) | +| `.display_modes.recent_display_mode` | `switch` \| `scroll` | `switch` (**Advanced**) | +| `.display_modes.upcoming_display_mode` | `switch` \| `scroll` | `switch` (**Advanced**) | + +### Filtering + +| Key | Type | Default | What it does | +|---|---|---|---| +| `.filtering.show_favorite_teams_only` | boolean | `true` | Show only your teams' games. | +| `.filtering.show_all_live` | boolean | `false` | Show every live game regardless of favorites. `exclude_teams` still applies. | +| `.filtering.favorite_live_boost` | 1–5 | `2` | **Advanced.** Turns a favorite's live game gets per one turn for other live games. `1` is even rotation. | + +### Game limits + +All **Advanced**. See [The selection settings](#the-selection-settings). + +| Key | Type | Default | +|---|---|---| +| `.game_limits.recent_games_to_show` | 1–25 | `1` | +| `.game_limits.upcoming_games_to_show` | 1–25 | `1` | +| `.game_limits.other_recent_games_to_show` | 0–20 | `1` | +| `.game_limits.other_upcoming_games_to_show` | 0–20 | `1` | +| `.game_limits.other_rotation_interval_seconds` | 0–86400 s | `1800` | +| `.game_limits.other_games_min_quality` | `any` \| `ranked` | `ranked` | +| `.game_limits.other_games_divisions` | array | `["fbs"]` | + +### Durations + +All **Advanced**. + +| Key | Type | Default | What it does | +|---|---|---|---| +| `.live_game_duration` | 10–120 s | `20` | Per-game time for live games. Applies to games with a favorite when a non-favorite duration is set. | +| `.non_favorite_live_game_duration` | 0–120 s | `0` | Shorter turn for live games with no favorite. `0` means use `live_game_duration` for everything. | +| `.display_durations.base` | 1–120 s | `15` | Fallback per-game time. | +| `.display_durations.live` | 1–120 s | `20` | Per-game time for live games. | +| `.display_durations.recent` | 1–120 s | `15` | Per-game time on the Recent screen. | +| `.display_durations.upcoming` | 1–120 s | `15` | Per-game time on the Upcoming screen. | -`non_favorite_live_game_duration` (0-120, default 0 = off) gives live games that -involve **none** of your favorite teams a shorter on-screen turn than your -favorites. For example `live_game_duration: 30` with -`non_favorite_live_game_duration: 5` shows your teams for 30s each while everyone -else's games flash by in 5s. +### Update intervals -This **only takes effect** when favorite teams are configured **and** -non-favorite live games are being shown — `show_favorite_teams_only` off, or -`show_all_live` on (otherwise non-favorite games are never on screen to -shorten). Leave it at `0` to display every live game for `live_game_duration`. +All **Advanced**. -| Favorite teams set? | Non-favorite games shown? | Live game has a favorite? | Duration used | +| Key | Type | Default | What it does | |---|---|---|---| -| No | — | — | `live_game_duration` (unchanged) | -| Yes | No (`show_favorite_teams_only` on, `show_all_live` off) | favorite | `live_game_duration` | -| Yes | Yes (`show_favorite_teams_only` off, or `show_all_live` on) | favorite | `live_game_duration` | -| Yes | Yes (`show_favorite_teams_only` off, or `show_all_live` on) | none | `non_favorite_live_game_duration` (when > 0) | +| `.update_interval_seconds` | 30–86400 s | `3600` | This league's base fetch cadence. | +| `.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. | +| `.stale_game_timeout` | 60–3600 s | `300` | Drop a live game the API has stopped updating. | + +### Display options + +| Key | Type | Default | What it does | +|---|---|---|---| +| `.display_options.show_records` | boolean | `false` | Draw win-loss records in the bottom corners. | +| `.display_options.show_ranking` | boolean | `false` NBA/WNBA, `true` college | Draw poll rank badges. Replaced by seeds on tournament cards when `march_madness.show_seeds` is on. | +| `.display_options.show_odds` | boolean | `true` | Draw betting odds. | + +![show_records on and off](../../docs/assets/basketball-scoreboard/show-records.png) + +### Mode durations -## Excluding Teams (Spoiler Protection) +How long the *whole mode* holds the board before the core rotates on. `null` +uses the dynamic calculation. Both **Advanced**, and read by the LEDMatrix core +rather than by this plugin, which is why they do not appear in the plugin's own +source. -`exclude_teams` (per league, same format as `favorite_teams`, e.g. `["LAL"]`) hides specific teams from both the live rotation and Recent/Final scores — handy if you're planning to watch a game delayed and don't want the result spoiled. Exclusion always wins: if a team appears in both `favorite_teams` and `exclude_teams`, it's excluded. Upcoming/schedule listings are unaffected since they carry no result to spoil. +| Key | Type | Default | +|---|---|---| +| `.mode_durations.recent_mode_duration` | 10–600 s or `null` | `null` | +| `.mode_durations.upcoming_mode_duration` | 10–600 s or `null` | `null` | + +> There is **no `live_mode_duration`** here, unlike the football, hockey and +> lacrosse scoreboards. Live mode's total is governed by dynamic duration or by +> `display_durations.live` per game. + +### Dynamic duration + +Sizes each mode's total time from how much there is to show. All **Advanced**. + +| Key | Type | Default | +|---|---|---| +| `.dynamic_duration.enabled` | boolean | `false` | +| `.dynamic_duration.max_duration_seconds` | 60–600 s | — | +| `.dynamic_duration.modes.live.enabled` | boolean | `false` | +| `.dynamic_duration.modes.live.max_duration_seconds` | 60–600 s | — | +| `.dynamic_duration.modes.recent.enabled` | boolean | `false` | +| `.dynamic_duration.modes.recent.max_duration_seconds` | 60–600 s | — | +| `.dynamic_duration.modes.upcoming.enabled` | boolean | `false` | +| `.dynamic_duration.modes.upcoming.max_duration_seconds` | 60–600 s | — | + +### Scroll settings + +All **Advanced**, and per league. + +| Key | Type | Default | What it does | +|---|---|---|---| +| `.scroll_settings.scroll_speed` | 1.0–200.0 px/s | `50.0` | Higher scrolls faster. | +| `.scroll_settings.scroll_delay` | 0.001–0.1 s | `0.01` | Frame delay; `0.01` is 100 FPS. Lower is smoother. | +| `.scroll_settings.gap_between_games` | 8–128 px | `48` | Gap between game cards. | +| `.scroll_settings.show_league_separators` | boolean | `true` | Draw league icons between leagues. | +| `.scroll_settings.dynamic_duration` | boolean | `true` | Size the scroll duration from the content width. | +| `.scroll_settings.game_card_width` | 32–512 px | `128` | Card width. Lower it on a multi-panel chain to fit more games on screen at once. | + +## Matchup separator and the upcoming card middle + +The **Matchup Card Layout** section (`scroll_card`) controls what sits between +the two crests before a game starts, and how the date and time are written. +Plugin-wide, not per league. + +| Setting | Key | Default | What it does | +|---|---|---|---| +| Matchup Separator | `scroll_card.vs_text` | `VS` | Text between the teams: `VS`, `@`, `at`, `v`. The away side is always on the left, so `@` and `at` read as "away at home". Blank draws nothing. | +| Middle of an Upcoming Card | `scroll_card.upcoming_center` | `vs` | Scroll and Vegas cards: `vs`, `date_time`, or `none`. | +| Middle of a Full-Screen Upcoming Scoreboard | `scroll_card.switch_upcoming_center` | `date_time` | The same choice for the full-screen scoreboard, plus `inherit` to follow the row above. | +| 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. | +| 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. | + +The centre-gap settings size the scroll and Vegas card's middle strip only — the +full-screen scoreboard pins its crests to the panel edges and is unaffected. + +| Key | Type | Default | What it does | +|---|---|---|---| +| `scroll_card.center_gap` | 0–64 px | unset | Pixels kept clear down the middle. Unset scales with card width; `0` restores edge-to-edge logos. | +| `scroll_card.center_gap_ratio` | 0.0–0.6 | `0.28` | **Advanced.** Fraction of card width used when the gap is not pinned. | +| `scroll_card.center_gap_min` | 0–64 px | `22` | **Advanced.** Floor for the scaled gap. | +| `scroll_card.center_gap_max` | 0–96 px | `40` | **Advanced.** Ceiling for the scaled gap. | + +## Fonts, colours and layout + +Seven text elements, each with `font`, `font_size`, and `text_color`, under +`customization.`. All **Advanced**. + +| Element | Default font | Default size | Draws | +|---|---|---|---| +| `score_text` | `PressStart2P-Regular.ttf` | `10` | The score, and the matchup separator on an upcoming card | +| `period_text` | `PressStart2P-Regular.ttf` | `8` | The quarter, and the date/time on an upcoming scoreboard | +| `team_name` | `PressStart2P-Regular.ttf` | `8` | Team names and abbreviations | +| `status_text` | `4x6-font.ttf` | `6` | Status lines such as "Next Game" and the tournament round | +| `detail_text` | `4x6-font.ttf` | `6` | Small detail lines | +| `rank_text` | `PressStart2P-Regular.ttf` | `10` | Rank badges and tournament seeds | +| `odds_text` | `4x6-font.ttf` | `6` | Betting odds (defaults to green, `[0, 255, 0]`) | + +Colours are `[r, g, b]` or `"#RRGGBB"`. Every default is white except +`odds_text`. Odds sizes snap to the face's pixel grid to stay crisp: +`4x6-font.ttf` to 7, 14, 21; press_start to 8, 16. Every +`customization.` object sets `additionalProperties: false`. ```json { - "nba": { - "exclude_teams": ["LAL"], - "filtering": { - "favorite_live_boost": 3 - } + "customization": { + "score_text": { "text_color": [255, 200, 0] }, + "status_text": { "text_color": "#00A0FF" } } } ``` -## Favorite Team Result Colors +### Layout offsets + +Nudge any element in pixels. All default to `0`, all **Advanced**, all under +`customization.layout.`, and all set `additionalProperties: false`. + +| Element | Keys | Measured from | +|---|---|---| +| `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 | +| `odds` | `x_offset`, `y_offset` | Default odds position | + +## Favorite team result colours A run of games against the same opponent is hard to read at a glance: in scroll -and Vegas mode the same two logos go past several times and only the digits -change. Turn on **Customization -> Favorite Team Result Colors** to color a -finished game's score by how your favorite team did - green for a win, red for -a loss. +and Vegas mode the same two crests go past several times and only the digits +change. Turn this on to colour a finished game's score by how your team did. + +| Key | Type | Default | +|---|---|---| +| `customization.favorite_result_colors.enabled` | boolean | `false` | +| `customization.favorite_result_colors.win_color` | `[r, g, b]` | `[0, 255, 0]` | +| `customization.favorite_result_colors.loss_color` | `[r, g, b]` | `[255, 0, 0]` | +| `customization.favorite_result_colors.tie_color` | `[r, g, b]` | `[255, 200, 0]` | ```json { @@ -407,55 +508,19 @@ a loss. } ``` -- Off by default. Until you enable it the score keeps exactly the color it has - today. -- Only finished games are colored. Live and upcoming cards are untouched. -- A game needs exactly one favorite team. If neither side is a favorite, or both - are, the score keeps its normal color. -- Applies to both the one-game-at-a-time switch view and the scroll/Vegas - ticker. -- The three colors are Advanced settings; leave them alone for the defaults - above. +- Only finished games are coloured; live and upcoming cards are untouched. +- A game needs **exactly one** favorite team. Neither side or both, and the score + keeps its normal colour. +- The three colours are Advanced settings. -## Troubleshooting - -- **Start times look like UTC** (a 6:45pm Central start showing as 11:45PM): - the plugin couldn't read your global timezone. Set `timezone` under the - plugin's Advanced Settings to your IANA zone, e.g. `America/Chicago`. -- **No games showing**: - - Check if leagues are enabled in configuration - - Verify API endpoints are accessible - - Check if favorite teams are configured (required for NCAA recent/upcoming modes) - - Review filtering settings - may be filtering out all games - -- **Missing team logos**: Ensure team logo files exist in your `assets/sports/` directory - -- **Slow updates**: - - Adjust `update_interval_seconds` in league configuration - - Adjust `live_update_interval` for live games - - Check network connectivity and API response times - -- **API errors**: - - Check your internet connection - - Verify ESPN API availability - - Review logs for specific error messages - - Check if rate limiting is occurring - -- **Live games not interrupting**: - - Verify `live_priority` is enabled for the league - - Check that there are actual live games available - - Review `has_live_content()` logs to see if live content is detected - -- **Too many/few games showing**: - - Adjust `game_limits.recent_games_to_show` and `game_limits.upcoming_games_to_show` - - Remember: with favorite teams, these are per-team limits - - Without favorite teams, these are total game limits +This tint is applied by the LEDMatrix core rather than by the plugin, which is +why the keys do not appear in this plugin's source. ## Vegas ticker: seeing live games more often -By default a live game **takes over** the display: the Vegas ticker stops and -this scoreboard shows full screen until the game ends. If you would rather keep -the marquee scrolling and still see scores, set this in the core config: +By default a live game **takes over** the display. To keep the marquee scrolling +and still see scores, set this in the **core** config — not in this plugin's +settings: ```json { @@ -470,134 +535,90 @@ the marquee scrolling and still see scores, set this in the core config: ``` The ticker is otherwise a strict round robin — every plugin appears once per -cycle — so with a dozen plugins enabled a score comes round once a lap. These -weights let this plugin claim several slots per cycle, spaced evenly through -it rather than bunched together. - -`live_weight` applies whenever this scoreboard has a live game. -`favorite_live_weight` applies when one of your `favorite_teams` is playing, so -your team's game comes round more often than other live games. That distinction +cycle. These weights let this plugin claim several slots per cycle, spaced +evenly through it. `live_weight` applies whenever this scoreboard has a live +game; `favorite_live_weight` when one of your teams is playing. That distinction has to be made here rather than in the core, which can tell *that* a game is 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 - 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 - fresh, which is governed by this plugin's own live update interval. - -## 🎯 Which Games Get Shown + using `favorite_live_boost`. +- More slots make the cycle **longer**, not faster. -**`upcoming_games_to_show` is not "how many cards you see".** It is the size of a *pool*. The panel cycles through that pool one card at a time and keeps its place between visits, so a pool of 3 means the board rotates through the same 3 games until the schedule moves on. Making the number bigger gives you a *longer lap*, so any one game comes round **less** often. +## Team abbreviations -Which mode you are in depends on whether `favorite_teams` is set and whether `show_favorite_teams_only` is on: +**NBA:** `LAL`, `BOS`, `GSW`, `MIL`, `PHI`, `DEN`, `MIA`, `BKN`, `ATL`, `CHA`, +`NYK`, `IND`, `DET`, `TOR`, `CHI`, `CLE`, `ORL`, `WAS`, `HOU`, `SAS`, `MIN`, +`POR`, `SAC`, `LAC`, `MEM`, `DAL`, `PHX`, `UTA`, `OKC`, `NOP`. -| `favorite_teams` | `show_favorite_teams_only` | What you get | -|---|---|---| -| empty | either | The next N games league-wide, chronologically. Every game shown is a non-favorite game, so the two filters below apply to all of them. | -| set | **on** | Only your teams. The limit is a budget **per team**. | -| set | **off** | **Your teams first, then other games to fill.** Both limits are **totals**. | +**WNBA:** `LVA` (Las Vegas Aces), `NYL` (New York Liberty), `CHI` (Chicago Sky), +`CONN` (Connecticut Sun), `DAL` (Dallas Wings), `ATL` (Atlanta Dream), `IND` +(Indiana Fever), `MIN` (Minnesota Lynx), `PHX` (Phoenix Mercury), `SEA` (Seattle +Storm), `WAS` (Washington Mystics), `LAC` (Los Angeles Sparks). -The third row is what most people want, and it did not exist before: with the flag off, favorites used to be ignored *entirely*. - -### The settings - -| Option | Default | Description | -|---|---|---| -| `upcoming_games_to_show` | varies | How many **favorite** upcoming games to show. | -| `recent_games_to_show` | varies | The same, for finished games. | -| `other_upcoming_games_to_show` | matches `upcoming_games_to_show` | How many **non-favorite** upcoming games to add. `0` gives you favorites only. | -| `other_recent_games_to_show` | matches `recent_games_to_show` | The same, for finished games. | -| `other_rotation_interval_seconds` | `1800` | How often the non-favorite slice advances. `0` pins it. | -| `other_games_min_quality` | `ranked` | Which non-favorite games qualify: `ranked`, `broadcast`, or `any`. | -| `other_games_divisions` | `["fbs"]` | Which divisions non-favorite games may come from. College football only — see the note below. | - -**Your favorite teams are never filtered by the last two** — follow a smaller-division team and its games always appear. Those settings only decide what fills the *remaining* slots. - -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 side, so a top-five matchup sits in the first window rather than whichever kicks off soonest — and the #1 team's whole season does not sort above everyone else's opener. Ties fall back to kickoff order, and a league with no poll keeps chronological order. Your favorite teams are ordered by when they play, not by rank -- for your own team the next game is the point. - -### Variety comes from turnover +**NCAA Men's:** `DUKE`, `UNC`, `KANSAS`, `KENTUCKY`, `UCLA`, `ARIZONA`, +`GONZAGA`, `BAYLOR`, `VILLANOVA`, `MICHIGAN`, `OHIOST`, `FLORIDA`, `WISCONSIN`, +`MARYLAND`, `VIRGINIA`, `LOUISVILLE`, `SYRACUSE`, `INDIANA`, `PURDUE`, `IOWA`. -Rather than widening the pool, the non-favorite slice **moves**: the window advances by its own width every `other_rotation_interval_seconds`, so consecutive windows do not overlap and the board works through the schedule instead of resampling the front of it. Your favorites are not rotated — for upcoming games the soonest ones are the point. +**NCAA Women's:** `UCONN`, `SCAR` (South Carolina), `STAN` (Stanford), `BAYLOR`, +`LOUISVILLE`, `OREGON`, `MISSST` (Mississippi State), `NDAME` (Notre Dame), +`DUKE`, `MARYLAND`, `UCLA`, `ARIZONA`, `OREGONST` (Oregon State), `FLORIDA`, +`TENNESSEE`, `TEXAS`, `OKLAHOMA`, `IOWA`. -Both filters **fail open**: if the data behind them cannot be fetched, the game is allowed through. A board showing filler is a poor board; a board showing nothing is a broken one. +To check any team, read `events[].competitions[].competitors[].team.abbreviation` +from the ESPN scoreboard endpoint for that league. -They fail open a second time, as a set: if the filters between them leave **nothing at all** — your teams idle and every other game rejected — the unfiltered list is used instead. Setting `other_upcoming_games_to_show` or `other_recent_games_to_show` to `0` is the one way to ask for an empty slate, and that is honoured. +## Installation -> `other_games_min_quality` needs a national poll, which only the college leagues publish — set to `ranked` in a professional league it lets every game through, and no poll is requested. `other_games_divisions` needs ESPN's FBS/FCS group rosters, which exist for **college football and nothing else**: asked for any other college league they come back empty or 500, so the setting is inert here and no lookup is made. +From the Plugin Store in the LEDMatrix web UI: open `http://your-pi-ip:5000`, go +to **Plugin Manager**, find **Basketball Scoreboard** under **Plugin Store**, +and click **Install**. Then open the plugin's tab to pick your leagues and +teams. -## Matchup separator and the upcoming card middle +The plugin requires the main LEDMatrix installation and inherits from its +basketball base classes. Crests download on first sight and cache under +`assets/sports/nba_logos/`, `assets/sports/wnba_logos/`, and +`assets/sports/ncaa_logos/`. -The **Matchup Card Layout** section (advanced) controls what sits between the -two team logos before a game starts, and how the date and time are written. -These settings now apply to every display mode -- the scroll ticker, the Vegas -ticker, and the full-screen scoreboard -- rather than only the tickers. +The documentation images come from `docs/assets/basketball-scoreboard/shots.json` +and re-render with `python scripts/render_docs_assets.py --plugin +basketball-scoreboard --check`. -| Setting | Key | Default | What it does | -|---|---|---|---| -| Matchup Separator | `vs_text` | `VS` | Text drawn between the teams: `VS`, `@`, `at`, `v`. The away team is always on the left, so `@` and `at` read as "away at home". Blank draws nothing. | -| Middle of an Upcoming Card | `upcoming_center` | `vs` | Scroll and Vegas cards: the separator, the date and time stacked, or nothing. | -| Middle of a Full-Screen Upcoming Scoreboard | `switch_upcoming_center` | `date_time` | The same choice for the full-screen scoreboard, plus `inherit` to follow the setting above. It defaults to the stacked date and time, which is what this display has always shown, so nothing changes until you pick something else. | -| Date Format | `date_format` | `abbrev` | How the scroll and Vegas cards write the date: `Sep 19`, `9/19`, `19 Sep`, `19/9`, or `Fri Sep 19`. | -| Full-Screen Date Format | `switch_date_format` | `numeric` | The same choice for the full-screen scoreboard, plus `inherit` to follow the row above. 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 | `time_format` | `12h` | 12- or 24-hour clock. | -| Show Date / Show Time | `show_date`, `show_time` | `true` | Drop either line. | -| Swap Date and Time | `swap_date_time` | `false` | Swap the two lines over. Each display starts from its own order, so this flips them rather than forcing one: the scroll and Vegas cards put the time on top, the full-screen date/time stack puts the date on top. | +## Troubleshooting -Choosing the separator for the full-screen scoreboard moves the date and time -out of the middle and onto the top and bottom rows, the same way the scroll -card lays them out; the "Next Game" header gives up the top row to them. +**Nothing appears.** Check that `enabled` is on, and that the league's own +`enabled` is on — only the NBA is on by default. -The center-gap settings in the same section size the scroll and Vegas card's -middle strip only -- the full-screen scoreboard pins its logos to the panel -edges and is unaffected. +**I disabled a mode and it still shows.** The keys here are `show_live`, +`show_recent`, and `show_upcoming` — not `live`, `recent`, `upcoming`. A +`display_modes` block copied from the hockey or lacrosse scoreboard sets +nothing. -Example: +**College Recent and Upcoming are nearly empty.** That is expected without +favorites — see [NCAA season data](#ncaa-season-data). Add favorite teams and +their full season schedules are fetched. -```json -{ - "scroll_card": { - "vs_text": "@", - "switch_upcoming_center": "vs", - "date_format": "weekday" - } -} -``` +**Tournament seeds or rounds do not show.** They only appear on games ESPN marks +as tournament games, and `march_madness.tournament_mode` is decided from the +calendar unless you set it explicitly. The round replaces the "Next Game" header, +so it shows on Upcoming cards. -### Text Colours +**The same few games keep repeating.** That is the pool cycling. Lower +`other_rotation_interval_seconds` for faster turnover rather than raising the +pool size — a larger pool makes the lap longer, so each game appears less often, +not more. -Each text element in the **Customization** section carries a colour, and it now -applies to the text drawn in that element's face — on the full-screen scoreboard -and on the scroll and Vegas cards alike. Until this version the picker changed -only which font was loaded; every string was drawn white. +**Start times look like UTC.** The plugin could not read your global timezone. +Set `timezone` under Advanced Settings to your IANA zone. -| Element | Key | Colours | -|---|---|---| -| Score | `score_text` | The score, and the matchup separator on an upcoming card | -| Period / clock | `period_text` | The clock, period, and the date and time on an upcoming scoreboard | -| Team name | `team_name` | Team names and abbreviations | -| Status | `status_text` | Status lines such as "Next Game" | -| Detail | `detail_text` | Small detail lines | -| Ranking | `rank_text` | Team rankings drawn in the ranking face | +**A finished game disappeared too soon.** Raise `schedule_lookback_days` +(default 14), or lower `recent_update_interval` if results are slow to appear. -Colours are `[r, g, b]` or `"#RRGGBB"`, and every default is white, so a display -nobody has recoloured looks exactly as it did. +**A game I know about never appears.** It may be beyond +`schedule_lookahead_days` (default 7). A game outside that horizon is never +fetched. -```json -{ - "customization": { - "score_text": { "text_color": [255, 200, 0] }, - "status_text": { "text_color": "#00A0FF" } - } -} -``` +## License -Two things keep their own colours on purpose: the betting-odds figures, which -are coloured by which side is favoured, and a finished game's score when -**Favorite Team Result Colors** is on — that tint wins, and your score colour -shows on every other game. Records and rankings drawn in the small fixed face -stay white; no element in the schema owns that face. +See `LICENSE`. diff --git a/plugins/basketball-scoreboard/manifest.json b/plugins/basketball-scoreboard/manifest.json index fa6a6f4e..fb65e93d 100644 --- a/plugins/basketball-scoreboard/manifest.json +++ b/plugins/basketball-scoreboard/manifest.json @@ -23,8 +23,11 @@ "version": "1.29.2", "released": "2026-09-02", "ledmatrix_min_version": "3.3.0", - "notes": "Render the font the settings page advertises. Each text element's schema names a default font, but several elements never passed it to the loader, so the loader used its own fallback instead: the status text -- and on hockey the detail and odds text too -- advertised the 4x6 font while actually drawing in PressStart2P. Choosing the font the picker already showed as active therefore changed the display, which looked like the picker was broken. Each element now renders what its schema declares. This affects panels 128 pixels wide and narrower, on upcoming screens only; wider panels draw that header in the time font and are unchanged. It also fixes a knock-on problem: \"Next Game\" needed 72px in the wrong font and had to shorten to \"Next\" on a 64px panel, where it now fits whole at 41px.", - "changelog": "Retry a team logo whose previous download failed, instead of showing a grey box forever. A failed download is cached by the core as a placeholder wearing the real logo's filename; the logo loader scans filename variations, found that stub, and so never called the downloader again. The loader now skips a placeholder that is stale enough to be worth retrying and lets the download run, which also picks up stubs already on disk. The retry is rate-limited by the core (6h), so this does not trade a permanent grey box for a request every frame. Needs a core carrying src.logo_downloader.is_placeholder_logo; against an older core the check is skipped and behaviour is unchanged. Ported byte-identically across every sports lineage." + "notes": "Fix every grid-snapped font rendering a pixel narrow. The shared code reads this plugin's config_schema.json to tell a default font size from one the user chose; a default gets snapped to the font's pixel grid, a choice is left alone. It located the schema by inspecting loaded modules, which fails under the real plugin loader -- the loader renames a plugin's modules and removes their original names, so nothing was left to inspect. The lookup returned nothing, every size then looked user-chosen, and the snap was skipped: 4x6-font.ttf drew at 6 instead of 7, which is 3-pixel-wide glyphs instead of 4. On a 256x64 panel the betting odds, team records and the date row were hard to read. The plugin now tells the shared code where it lives instead of leaving it to guess.", + "changelog": "Retry a team logo whose previous download failed, instead of showing a grey box forever. A failed download is cached by the core as a placeholder wearing the real logo's filename; the logo loader scans filename variations, found that stub, and so never called the downloader again. The loader now skips a placeholder that is stale enough to be worth retrying and lets the download run, which also picks up stubs already on disk. The retry is rate-limited by the core (6h), so this does not trade a permanent grey box for a request every frame. Needs a core carrying src.logo_downloader.is_placeholder_logo; against an older core the check is skipped and behaviour is unchanged. Ported byte-identically across every sports lineage.", + "changes": [ + "Rewrote the README as a complete settings reference covering all 274 settings, with rendered examples of every display mode and the March Madness tournament card." + ] }, { "version": "1.29.1", @@ -331,7 +334,7 @@ { "released": "2026-07-02", "version": "1.6.0", - "notes": "Add exclude_teams (hide specific teams from live rotation and recent/final scores — spoiler protection) and filtering.favorite_live_boost (tune how much more often your favorite's live game appears in rotation vs other live games) per league.", + "notes": "Add exclude_teams (hide specific teams from live rotation and recent/final scores \u2014 spoiler protection) and filtering.favorite_live_boost (tune how much more often your favorite's live game appears in rotation vs other live games) per league.", "ledmatrix_min": "2.0.0" }, {