166 lines
4.8 KiB
Python
166 lines
4.8 KiB
Python
import time
|
|
import requests
|
|
import os
|
|
from PIL import Image
|
|
from rgbmatrix import RGBMatrix, RGBMatrixOptions, graphics
|
|
|
|
# --- Matrix config ---
|
|
options = RGBMatrixOptions()
|
|
options.rows = 32
|
|
options.cols = 64
|
|
options.chain_length = 4
|
|
options.parallel = 1
|
|
options.hardware_mapping = 'regular'
|
|
options.gpio_slowdown = 5
|
|
options.disable_hardware_pulsing = True
|
|
options.brightness = 80
|
|
|
|
matrix = RGBMatrix(options=options)
|
|
canvas = matrix.CreateFrameCanvas()
|
|
|
|
font = graphics.Font()
|
|
font.LoadFont("/usr/local/share/7x13.bdf")
|
|
|
|
# A smaller font for status text
|
|
font_small = graphics.Font()
|
|
font_small.LoadFont("/usr/local/share/5x7.bdf")
|
|
|
|
white = graphics.Color(255, 255, 255)
|
|
yellow = graphics.Color(255, 200, 0)
|
|
red = graphics.Color(255, 50, 50)
|
|
grey = graphics.Color(180, 180, 180)
|
|
|
|
LOGO_DIR = "/usr/local/share/logos"
|
|
|
|
# --- Logo cache ---
|
|
logo_cache = {}
|
|
|
|
def load_logo(league, abbr):
|
|
key = f"{league}_{abbr}"
|
|
if key in logo_cache:
|
|
return logo_cache[key]
|
|
|
|
path = os.path.join(LOGO_DIR, f"{key}.png")
|
|
print(f"Looking for logo at: {path}, exists: {os.path.exists(path)}")
|
|
|
|
if not os.path.exists(path):
|
|
logo_cache[key] = None
|
|
return None
|
|
|
|
try:
|
|
img = Image.open(path).convert("RGB")
|
|
logo_cache[key] = img
|
|
return img
|
|
except Exception as e:
|
|
print(f"Error loading logo {key}: {e}")
|
|
logo_cache[key] = None
|
|
return None
|
|
|
|
def draw_logo(canvas, img, x, y):
|
|
if img is None:
|
|
print("Logo is None!")
|
|
return
|
|
print(f"Drawing logo {img.size} at {x},{y}")
|
|
for px in range(img.width):
|
|
for py in range(img.height):
|
|
r, g, b = img.getpixel((px, py))
|
|
canvas.SetPixel(x + px, y + py, r, g, b)
|
|
|
|
# --- Fetch scores ---
|
|
def get_scores(sport, league):
|
|
url = f"https://site.api.espn.com/apis/site/v2/sports/{sport}/{league}/scoreboard"
|
|
try:
|
|
resp = requests.get(url, timeout=5)
|
|
resp.raise_for_status()
|
|
games = []
|
|
for event in resp.json().get("events", []):
|
|
comp = event["competitions"][0]
|
|
teams = comp["competitors"]
|
|
home = next(t for t in teams if t["homeAway"] == "home")
|
|
away = next(t for t in teams if t["homeAway"] == "away")
|
|
status = event["status"]["type"]["shortDetail"]
|
|
games.append({
|
|
"league": league,
|
|
"away": away["team"]["abbreviation"].upper(),
|
|
"away_score": away["score"],
|
|
"home": home["team"]["abbreviation"].upper(),
|
|
"home_score": home["score"],
|
|
"status": status,
|
|
})
|
|
return games
|
|
except Exception as e:
|
|
print(f"Fetch error ({league}): {e}")
|
|
return []
|
|
|
|
def get_all_scores():
|
|
games = []
|
|
games += get_scores("hockey", "nhl")
|
|
games += get_scores("football", "nfl")
|
|
games += get_scores("basketball", "nba")
|
|
return games
|
|
|
|
# --- Draw a single game in stacked layout ---
|
|
# Layout per panel (64px wide, 32px tall):
|
|
# Top half (rows 0-15): [16x16 away logo] [away abbr] [away score]
|
|
# Bottom half (rows 16-31): [16x16 home logo] [home abbr] [home score]
|
|
# Status text scrolls across the bottom row
|
|
|
|
def draw_game(canvas, game):
|
|
league = game["league"]
|
|
|
|
away_logo = load_logo(league, game["away"])
|
|
home_logo = load_logo(league, game["home"])
|
|
|
|
# Draw logos
|
|
draw_logo(canvas, away_logo, 0, 0) # top left
|
|
draw_logo(canvas, home_logo, 0, 16) # bottom left
|
|
|
|
# Draw team abbreviations
|
|
graphics.DrawText(canvas, font_small, 18, 11, white, game["away"])
|
|
graphics.DrawText(canvas, font_small, 18, 27, white, game["home"])
|
|
|
|
# Draw scores
|
|
graphics.DrawText(canvas, font, 40, 13, yellow, str(game["away_score"]))
|
|
graphics.DrawText(canvas, font, 40, 29, yellow, str(game["home_score"]))
|
|
|
|
# Draw status in grey at far right if short enough, otherwise truncate
|
|
status = game["status"][:10]
|
|
graphics.DrawText(canvas, font_small, 130, 11, grey, status)
|
|
|
|
# --- Main loop ---
|
|
def run():
|
|
global canvas
|
|
games = []
|
|
last_fetch = 0
|
|
current_game = 0
|
|
game_display_time = 8 # seconds per game
|
|
last_switch = time.time()
|
|
|
|
while True:
|
|
now = time.time()
|
|
|
|
# Re-fetch every 30 seconds
|
|
if now - last_fetch > 30:
|
|
games = get_all_scores()
|
|
last_fetch = now
|
|
if not games:
|
|
current_game = 0
|
|
|
|
# Switch game every N seconds
|
|
if games and now - last_switch > game_display_time:
|
|
current_game = (current_game + 1) % len(games)
|
|
last_switch = now
|
|
|
|
canvas.Clear()
|
|
|
|
if games:
|
|
current_game = current_game % len(games)
|
|
draw_game(canvas, games[current_game])
|
|
else:
|
|
graphics.DrawText(canvas, font, 10, 22, red, "No games today")
|
|
|
|
canvas = matrix.SwapOnVSync(canvas)
|
|
time.sleep(0.03)
|
|
|
|
if __name__ == "__main__":
|
|
run() |