Skip to content

Commit

Permalink
Merge pull request #2623 from adafruit/sportball
Browse files Browse the repository at this point in the history
adding code for the ESPN API project
  • Loading branch information
TheKitty authored Sep 15, 2023
2 parents 74f5d77 + 3886113 commit b92b2f4
Show file tree
Hide file tree
Showing 2 changed files with 497 additions and 0 deletions.
367 changes: 367 additions & 0 deletions Matrix_Portal_S3_ESPN_API/CircuitPython/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,367 @@
# SPDX-FileCopyrightText: 2023 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import os
import gc
import ssl
import time
import wifi
import socketpool
import adafruit_requests
import adafruit_display_text.label
import board
import terminalio
import displayio
import framebufferio
import rgbmatrix
import adafruit_json_stream as json_stream
import microcontroller
from adafruit_ticks import ticks_ms, ticks_add, ticks_diff
from adafruit_datetime import datetime, timedelta
import neopixel

displayio.release_displays()

# font color for text on matrix
font_color = 0xFFFFFF
# your timezone UTC offset and timezone name
timezone_info = [-4, "EDT"]
# the name of the sports you want to follow
sport_name = ["football", "baseball", "soccer", "hockey", "basketball"]
# the name of the corresponding leages you want to follow
sport_league = ["nfl", "mlb", "usa.1", "nhl", "nba"]
# the team names you want to follow
# must match the order of sport/league arrays
# include full name and then abbreviation (usually city/region)
team0 = ["New England Patriots", "NE"]
team1 = ["Boston Red Sox", "BOS"]
team2 = ["New England Revolution", "NE"]
team3 = ["Boston Bruins", "BOS"]
team4 = ["Boston Celtics", "BOS"]
# how often the API should be fetched
fetch_timer = 300 # seconds
# how often the display should update
display_timer = 30 # seconds

pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness = 0.3, auto_write=True)

# matrix setup
base_width = 64
base_height = 32
chain_across = 2
tile_down = 2
DISPLAY_WIDTH = base_width * chain_across
DISPLAY_HEIGHT = base_height * tile_down
matrix = rgbmatrix.RGBMatrix(
width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, bit_depth=3,
rgb_pins=[
board.MTX_R1,
board.MTX_G1,
board.MTX_B1,
board.MTX_R2,
board.MTX_G2,
board.MTX_B2
],
addr_pins=[
board.MTX_ADDRA,
board.MTX_ADDRB,
board.MTX_ADDRC,
board.MTX_ADDRD
],
clock_pin=board.MTX_CLK,
latch_pin=board.MTX_LAT,
output_enable_pin=board.MTX_OE,
tile=tile_down, serpentine=True,
doublebuffer=False
)
display = framebufferio.FramebufferDisplay(matrix)

# connect to WIFI
wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD"))
print(f"Connected to {os.getenv('CIRCUITPY_WIFI_SSID')}")

# add API URLs
SPORT_URLS = []
for i in range(5):
d = (
f"https://site.api.espn.com/apis/site/v2/sports/{sport_name[i]}/{sport_league[i]}/scoreboard"
)
SPORT_URLS.append(d)

context = ssl.create_default_context()
pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, context)

# arrays for teams, logos and display groups
teams = []
logos = []
groups = []
# add team to array
teams.append(team0)
# grab logo bitmap name
logo0 = "/team0_logos/" + team0[1] + ".bmp"
# add logo to array
logos.append(logo0)
# create a display group
group0 = displayio.Group()
# add group to array
groups.append(group0)
# repeat:
teams.append(team1)
logo1 = "/team1_logos/" + team1[1] + ".bmp"
logos.append(logo1)
group1 = displayio.Group()
groups.append(group1)
teams.append(team2)
logo2 = "/team2_logos/" + team2[1] + ".bmp"
logos.append(logo2)
group2 = displayio.Group()
groups.append(group2)
teams.append(team3)
logo3 = "/team3_logos/" + team3[1] + ".bmp"
logos.append(logo3)
group3 = displayio.Group()
groups.append(group3)
teams.append(team4)
logo4 = "/team4_logos/" + team4[1] + ".bmp"
logos.append(logo4)
group4 = displayio.Group()
groups.append(group4)

# initial startup screen
# shows the five team logos you are following
def sport_startup(logo):
try:
group = displayio.Group()
bitmap0 = displayio.OnDiskBitmap(logo[0])
grid0 = displayio.TileGrid(bitmap0, pixel_shader=bitmap0.pixel_shader, x = 0)
bitmap1 = displayio.OnDiskBitmap(logo[1])
grid1 = displayio.TileGrid(bitmap1, pixel_shader=bitmap1.pixel_shader, x = 32)
bitmap2 = displayio.OnDiskBitmap(logo[2])
grid2 = displayio.TileGrid(bitmap2, pixel_shader=bitmap2.pixel_shader, x = 64)
bitmap3 = displayio.OnDiskBitmap(logo[3])
grid3 = displayio.TileGrid(bitmap3, pixel_shader=bitmap3.pixel_shader, x = 96)
bitmap4 = displayio.OnDiskBitmap(logo[4])
grid4 = displayio.TileGrid(bitmap4, pixel_shader=bitmap4.pixel_shader, x = 48, y=32)
group.append(grid0)
group.append(grid1)
group.append(grid2)
group.append(grid3)
group.append(grid4)
display.show(group)
# pylint: disable=broad-except
except Exception:
print("Can't find bitmap. Did you run the get_team_logos.py script?")

# takes UTC time from JSON and reformats how its displayed
def convert_date_format(date, tz_information):
# Manually extract year, month, day, hour, and minute from the string
year = int(date[0:4])
month = int(date[5:7])
day = int(date[8:10])
hour = int(date[11:13])
minute = int(date[14:16])
# Construct a datetime object using the extracted values
dt = datetime(year, month, day, hour, minute)
# Adjust the datetime object for the target timezone offset
dt_adjusted = dt + timedelta(hours=tz_information[0])
# Extract fields for output format
month = dt_adjusted.month
day = dt_adjusted.day
hour = dt_adjusted.hour
minute = dt_adjusted.minute
# Convert 24-hour format to 12-hour format and determine AM/PM
am_pm = "AM" if hour < 12 else "PM"
hour_12 = hour if hour <= 12 else hour - 12
# Determine the timezone abbreviation based on the offset
time_zone_str = tz_information[1]
return f"{month}/{day} - {hour_12}:{minute} {am_pm} {time_zone_str}"

# the actual API and display function
# pylint: disable=too-many-locals, too-many-branches, too-many-statements
def get_data(data, team, logo, group):
pixel.fill((0, 0, 255))
print(f"Fetching data from {data}")
playing = False
names = []
scores = []
info = []
# the team you are following's logo
bitmap0 = displayio.OnDiskBitmap(logo)
grid0 = displayio.TileGrid(bitmap0, pixel_shader=bitmap0.pixel_shader, x = 2)
home_text = adafruit_display_text.label.Label(terminalio.FONT, color=font_color,
text=" ")
away_text = adafruit_display_text.label.Label(terminalio.FONT, color=font_color,
text=" ")
vs_text = adafruit_display_text.label.Label(terminalio.FONT, color=font_color,
text=" ")
vs_text.anchor_point = (0.5, 0.0)
vs_text.anchored_position = (DISPLAY_WIDTH / 2, 14)
info_text = adafruit_display_text.label.Label(terminalio.FONT, color=font_color,
text=" ")
info_text.anchor_point = (0.5, 1.0)
info_text.anchored_position = (DISPLAY_WIDTH / 2, DISPLAY_HEIGHT)
# make the request to the API
resp = requests.get(data)
# stream the json
json_data = json_stream.load(resp.iter_content(32))
for event in json_data["events"]:
# clear the date and then add the date to the array
# the date for your game will remain
info.clear()
info.append(event["date"])
# check for your team playing
if team[0] not in event["name"]:
continue
for competition in event["competitions"]:
for competitor in competition["competitors"]:
# if your team is playing:
playing = True
# get team names
# index indicates home vs. away
names.append(competitor["team"]["abbreviation"])
# the current score
scores.append(competitor["score"])
# gets info on game
info.append(event["status"]["type"]["shortDetail"])
break
# debug printing
print(names)
print(scores)
print(info)
if playing:
# pull out the date
date = info[0]
# convert it to be readable
date = convert_date_format(date, timezone_info)
print(date)
# pull out the info
info = info[1]
# check if it's pre-game
if str(info) == date or str(info) == "Scheduled":
status = "pre"
print("match, pre-game")
else:
status = info
# home and away text
# teams index determines which team is home or away
home_text.text="HOME"
away_text.text="AWAY"
if team[1] is names[0]:
home_game = True
home_text.anchor_point = (0.0, 0.5)
home_text.anchored_position = (5, 37)
away_text.anchor_point = (1.0, 0.5)
away_text.anchored_position = (124, 37)
vs_team = names[1]
else:
home_game = False
away_text.anchor_point = (0.0, 0.5)
away_text.anchored_position = (5, 37)
home_text.anchor_point = (1.0, 0.5)
home_text.anchored_position = (124, 37)
vs_team = names[0]
# if it's pre-game, show "VS"
if status == "pre":
vs_text.text="VS"
info_text.text=date
# if it's active or final show score
else:
info_text.text=info
if home_game:
vs_text.text=f"{scores[0]} - {scores[1]}"
else:
vs_text.text=f"{scores[1]} - {scores[0]}"
# load in logo from other team
vs_logo = logo.replace(team[1], vs_team)
# if there is no game matching your team:
else:
status = "pre"
vs_logo = logo
info_text.text="NO DATA AVAILABLE"
# load in the other team's logo
bitmap1 = displayio.OnDiskBitmap(vs_logo)
grid1 = displayio.TileGrid(bitmap1, pixel_shader=bitmap1.pixel_shader, x = 94)
print("done")
# update the display group. try/except in case its the first time it's being added
try:
group[0] = grid0
group[1] = grid1
group[2] = home_text
group[3] = away_text
group[4] = vs_text
group[5] = info_text
except IndexError:
group.append(grid0)
group.append(grid1)
group.append(home_text)
group.append(away_text)
group.append(vs_text)
group.append(info_text)
# close the response
resp.close()
pixel.fill((0, 0, 0))
# return that data was just fetched
fetch_status = True
return fetch_status

# index and clock for fetching
fetch_index = 0
fetch_timer = fetch_timer * 1000
# index and clock for updating display
display_index = 0
display_timer = display_timer * 1000
# load logos
sport_startup(logos)
# initial data fetch
for z in range(5):
try:
just_fetched = get_data(SPORT_URLS[z],
teams[z],
logos[z],
groups[z])
display.show(groups[z])
# pylint: disable=broad-except
except Exception as Error:
print(Error)
time.sleep(10)
gc.collect()
time.sleep(5)
microcontroller.reset()
# start clocks
just_fetched = True
fetch_clock = ticks_ms()
display_clock = ticks_ms()

while True:
try:
if not just_fetched:
# garbage collection for display groups
gc.collect()
# fetch the json for the next team
just_fetched = get_data(SPORT_URLS[fetch_index],
teams[fetch_index],
logos[fetch_index],
groups[fetch_index])
# advance index
fetch_index = (fetch_index + 1) % len(teams)
# reset clocks
fetch_clock = ticks_add(fetch_clock, fetch_timer)
display_clock = ticks_add(display_clock, display_timer)
# update display seperate from API request
if ticks_diff(ticks_ms(), display_clock) >= display_timer:
print("updating display")
display.show(groups[display_index])
display_index = (display_index + 1) % len(teams)
display_clock = ticks_add(display_clock, display_timer)
# cleared for fetching after time has passed
if ticks_diff(ticks_ms(), fetch_clock) >= fetch_timer:
just_fetched = False
# pylint: disable=broad-except
except Exception as Error:
print(Error)
time.sleep(10)
gc.collect()
time.sleep(5)
microcontroller.reset()
Loading

0 comments on commit b92b2f4

Please sign in to comment.