house cleaning

This commit is contained in:
2026-05-03 03:27:57 -04:00
parent 920e11acdf
commit ba47ae0952
10 changed files with 209809 additions and 170 deletions
+117 -145
View File
@@ -1,11 +1,19 @@
import time
import requests
import os
import govee
import pygame
from time import sleep
from enum import Enum
from time import sleep, time
from PIL import Image, ImageDraw, ImageFont
from rgbmatrix import RGBMatrix, RGBMatrixOptions, graphics
from dotenv import load_dotenv
# --- Load environment vars ---
load_dotenv()
# --- Default vars ---
ASSET_DIR = "./assets"
LOGO_DIR = os.path.join(ASSET_DIR, 'logos')
# --- Matrix config ---
options = RGBMatrixOptions()
@@ -21,49 +29,43 @@ options.brightness = 80
matrix = RGBMatrix(options=options)
canvas = matrix.CreateFrameCanvas()
# --- Font initialization ---
font = graphics.Font()
font.LoadFont("/usr/local/share/7x13.bdf")
font_small = graphics.Font()
font_small.LoadFont("/usr/local/share/5x7.bdf")
font_big = graphics.Font()
font.LoadFont(os.path.join(ASSET_DIR, 'fonts/7x13.bdf'))
font_small.LoadFont(os.path.join(ASSET_DIR, 'fonts/5x7.bdf'))
# try to load a big font for GOAL!, fall back to regular if not available
font_big = graphics.Font()
try:
font_big.LoadFont("/usr/local/share/9x18.bdf")
font_big.LoadFont(os.path.join(ASSET_DIR, 'fonts/9x18.bdf'))
except:
font_big = font
white = graphics.Color(255, 255, 255)
yellow = graphics.Color(255, 200, 0)
red = graphics.Color(255, 50, 50)
grey = graphics.Color(180, 180, 180)
sabres_blue = graphics.Color(0, 135, 48)
sabres_gold = graphics.Color(252, 20, 210)
LOGO_DIR = "/usr/local/share/logos"
SABRES_ABBR = "BUF"
GOVEE_DEVICE = "3D:22:D7:94:40:46:2F:72"
GOVEE_SKU = "H6168"
# --- Logo cache ---
logo_cache = {}
# --- Pre-built colors ---
class Colors(Enum):
WHITE = (255, 255, 255)
YELLOW = (255, 200, 0)
RED = (255, 50, 50)
SABRES_BLUE = (0, 135, 48)
SABRES_GOLD = (252, 20, 210)
# --- Govee API ---
govee_api = govee.GoveeApi(key="")
govee_api = govee.GoveeApi(key=os.environ['GOVEE_API_KEY'])
# --- PyGame Audio ---
pygame.mixer.init()
def play_goal_horn():
pygame.mixer.music.load("/usr/local/share/horn.mp3")
pygame.mixer.music.play()
# --- Goal celebrations ---
def render_goal_frame(text, text_scale, bg_color, text_color):
big_h = max(8, int(32 * text_scale))
big_img = Image.new("RGB", (1024, 128), bg_color)
big_draw = ImageDraw.Draw(big_img)
# rpi specific, fall back to default font if not existing
try:
pil_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", big_h)
except:
@@ -80,7 +82,6 @@ def render_goal_frame(text, text_scale, bg_color, text_color):
scaled = big_img.resize((256, 32), Image.LANCZOS)
# paste sabres logo on left and right
# Replace the logo paste section with this
logo_path = os.path.join(LOGO_DIR, "nhl_BUF.png")
if os.path.exists(logo_path):
try:
@@ -113,62 +114,70 @@ def render_goal_frame(text, text_scale, bg_color, text_color):
return scaled
def play_goal_celebration(text, color1, color2):
global canvas
# Phase 1: zoom in from tiny to full, alternating bg color
zoom_steps = [0.1, 0.2, 0.35, 0.5, 0.65, 0.8, 0.95, 1.1, 1.0]
for _ in range(5):
for i, scale in enumerate(zoom_steps):
bg = color1 if i % 2 == 0 else color2
fg = color2 if i % 2 == 0 else color1
frame = render_goal_frame(text, scale, bg, fg)
canvas.Clear()
draw_pil_image(canvas, frame)
canvas = matrix.SwapOnVSync(canvas)
sleep(0.05)
# Phase 2: rapid flashing at full size
for i in range(10):
bg = color1 if i % 2 == 0 else color2
fg = color2 if i % 2 == 0 else color1
frame = render_goal_frame(text, 1.0, bg, fg)
canvas.Clear()
draw_pil_image(canvas, frame)
canvas = matrix.SwapOnVSync(canvas)
sleep(0.12)
# Phase 3: zoom back out and fade to white flash
zoom_out = [1.0, 1.1, 1.2, 1.3, 1.4]
for i, scale in enumerate(zoom_out):
bg = color2 if i % 2 == 0 else color1
fg = color1 if i % 2 == 0 else color2
frame = render_goal_frame(text, scale, bg, fg)
canvas.Clear()
draw_pil_image(canvas, frame)
canvas = matrix.SwapOnVSync(canvas)
sleep(0.08)
# Phase 4: white flash to end
for _ in range(3):
canvas.Clear()
frame = render_goal_frame(text, 1.0, Colors.SABRES_GOLD, Colors.SABRES_BLUE)
draw_pil_image(canvas, frame)
canvas = matrix.SwapOnVSync(canvas)
sleep(0.1)
# clear board
canvas = canvas.clear()
canvas = matrix.SwapOnVSync(canvas)
# stop music if playing
pygame.mixer.music.stop()
# Hold for a moment then return to scoreboard
sleep(0.5)
def play_audio(filename):
pygame.mixer.music.load(os.path.join(ASSET_DIR, filename))
pygame.mixer.music.play()
# --- Utilities ---
def draw_pil_image(canvas, img):
for x in range(img.width):
for y in range(img.height):
r, g, b = img.getpixel((x, y))
canvas.SetPixel(x, y, b, g, r) # swap r and g for GRB panels
def play_goal_celebration():
global canvas
BLUE = (0, 135, 48) # was (0, 48, 135)
GOLD = (252, 20, 210) # was (252, 210, 20)
WHITE = (255, 255, 255) # unchanged
TEXT = "SABRES GOAL!"
# Phase 1: zoom in from tiny to full, alternating bg color
zoom_steps = [0.1, 0.2, 0.35, 0.5, 0.65, 0.8, 0.95, 1.1, 1.0]
for i, scale in enumerate(zoom_steps):
bg = BLUE if i % 2 == 0 else GOLD
fg = GOLD if i % 2 == 0 else BLUE
frame = render_goal_frame(TEXT, scale, bg, fg)
canvas.Clear()
draw_pil_image(canvas, frame)
canvas = matrix.SwapOnVSync(canvas)
time.sleep(0.05)
# Phase 2: rapid flashing at full size
for i in range(10):
bg = BLUE if i % 2 == 0 else GOLD
fg = GOLD if i % 2 == 0 else BLUE
frame = render_goal_frame(TEXT, 1.0, bg, fg)
canvas.Clear()
draw_pil_image(canvas, frame)
canvas = matrix.SwapOnVSync(canvas)
time.sleep(0.12)
# Phase 3: zoom back out and fade to white flash
zoom_out = [1.0, 1.1, 1.2, 1.3, 1.4]
for i, scale in enumerate(zoom_out):
bg = GOLD if i % 2 == 0 else BLUE
fg = BLUE if i % 2 == 0 else GOLD
frame = render_goal_frame(TEXT, scale, bg, fg)
canvas.Clear()
draw_pil_image(canvas, frame)
canvas = matrix.SwapOnVSync(canvas)
time.sleep(0.08)
# Phase 4: white flash to end
for _ in range(3):
canvas.Clear()
frame = render_goal_frame(TEXT, 1.0, WHITE, BLUE)
draw_pil_image(canvas, frame)
canvas = matrix.SwapOnVSync(canvas)
time.sleep(0.1)
# Hold for a moment then return to scoreboard
time.sleep(0.5)
canvas.SetPixel(x, y, b, g, r) # bgr panels
def load_logo(league, abbr):
key = f"{league}_{abbr}"
@@ -196,7 +205,7 @@ def draw_logo(canvas, img, 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, b, g) # RBG order
canvas.SetPixel(x + px, y + py, b, g, r) # bgr panels
# --- Fetch scores ---
def get_scores(sport, league):
@@ -230,35 +239,9 @@ def get_all_scores():
games += get_scores("hockey", "nhl")
games += get_scores("football", "nfl")
games += get_scores("basketball", "nba")
games += get_scores("baseball", "mlb")
return games
def sabres_scored(games, prev_scores):
for game in games:
if game["away"] != SABRES_ABBR and game["home"] != SABRES_ABBR:
continue
gid = game["id"]
try:
away = int(game["away_score"])
home = int(game["home_score"])
except ValueError:
continue
if gid not in prev_scores:
continue
prev_away, prev_home = prev_scores[gid]
if game["away"] == SABRES_ABBR and away > prev_away:
return True
if game["home"] == SABRES_ABBR and home > prev_home:
return True
return False
# --- Goal celebration ---
def fill_background(canvas, color):
for x in range(256):
for y in range(32):
canvas.SetPixel(x, y, *color)
# --- Draw all games across all panels ---
def draw_all_games(canvas, games, start_index):
for i in range(4):
@@ -273,11 +256,11 @@ def draw_all_games(canvas, games, start_index):
draw_logo(canvas, away_logo, offset + 0, 0)
draw_logo(canvas, home_logo, offset + 0, 16)
graphics.DrawText(canvas, font_small, offset + 18, 11, white, game["away"])
graphics.DrawText(canvas, font_small, offset + 18, 27, white, game["home"])
graphics.DrawText(canvas, font_small, offset + 18, 11, Colors.WHITE, game["away"])
graphics.DrawText(canvas, font_small, offset + 18, 27, Colors.WHITE, game["home"])
graphics.DrawText(canvas, font, offset + 40, 13, yellow, str(game["away_score"]))
graphics.DrawText(canvas, font, offset + 40, 29, yellow, str(game["home_score"]))
graphics.DrawText(canvas, font, offset + 40, 13, Colors.YELLOW, str(game["away_score"]))
graphics.DrawText(canvas, font, offset + 40, 29, Colors.YELLOW, str(game["home_score"]))
if i < 3:
for row in range(32):
@@ -291,51 +274,40 @@ def run():
last_fetch = 0
current_page = 0
page_display_time = 8
last_switch = time.time()
last_switch = time()
while True:
play_goal_horn()
govee_api.set_diy_scene(GOVEE_SKU, GOVEE_DEVICE)
play_goal_celebration()
now = time.time()
sleep(60)
if now - last_fetch > 30:
new_games = get_all_scores()
# while True:
# now = time.time()
# update prev_scores
for game in new_games:
gid = game["id"]
try:
prev_scores[gid] = (int(game["away_score"]), int(game["home_score"]))
except ValueError:
pass
# if now - last_fetch > 30:
# new_games = get_all_scores()
games = new_games
last_fetch = now
if not games:
current_page = 0
# # check for sabres goal before updating prev_scores
# if prev_scores and sabres_scored(new_games, prev_scores):
# play_goal_celebration()
if games and now - last_switch > page_display_time:
current_page = (current_page + 4) % max(len(games), 1)
last_switch = now
# # update prev_scores
# for game in new_games:
# gid = game["id"]
# try:
# prev_scores[gid] = (int(game["away_score"]), int(game["home_score"]))
# except ValueError:
# pass
canvas.Clear()
# games = new_games
# last_fetch = now
# if not games:
# current_page = 0
if games:
draw_all_games(canvas, games, current_page)
else:
graphics.DrawText(canvas, font, 10, 22, Colors.RED, "No games today")
# if games and now - last_switch > page_display_time:
# current_page = (current_page + 4) % max(len(games), 1)
# last_switch = now
# canvas.Clear()
# if games:
# draw_all_games(canvas, games, current_page)
# else:
# graphics.DrawText(canvas, font, 10, 22, red, "No games today")
# canvas = matrix.SwapOnVSync(canvas)
# time.sleep(0.03)
canvas = matrix.SwapOnVSync(canvas)
sleep(0.03)
if __name__ == "__main__":
run()