Skip to content

Commit

Permalink
Merge pull request #194 from MLB-LED-Scoreboard/dev
Browse files Browse the repository at this point in the history
Release 2.0.0
  • Loading branch information
ajbowler authored Jul 30, 2018
2 parents 4741b28 + 41952af commit d8c793d
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 104 deletions.
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,20 +100,30 @@ See the Flags section below for more flags you can optionally provide.
A default `config.json.example` file is included for reference. Copy this file to `config.json` and modify the values as needed.

```
"preferred_team" String Pick a team to display a game for. Example: "Cubs"
"preferred_division" String Pick a division to display standings for when display_standings is true. Example: "NL Central"
"display_standings" Bool Display standings for the provided preferred_division.
"display_standings_on_offday" Bool Display standings for the provided preferred division when there are no games on the current day.
Integer If 0, same as false. If 1, Same as the above. If 2, the standings are displayed if your preferred team has no games, instead of all teams.
"rotate_games" Bool Rotate through each game of the day every 15 seconds.
"rotate_rates" Dict Dictionary of Floats. Each type of screen can use a different rotation rate. Valid types: "live", "pregame", "final".
Float A Float can be used to set all screen types to the same rotate rate.
"stay_on_live_preferred_team" Bool Stop rotating through games when your preferred team is currently live.
"scroll_until_finished" Bool If scrolling text takes longer than the rotation rate, wait to rotate until scrolling is done.
"end_of_day" String A 24-hour time you wish to consider the end of the previous day before starting to display the current day's games. Uses local time from your pi.
"display_full_team_names" Bool If true and on a 64-wide board, displays the full team name on the scoreboard instead of their abbreviation. This config option is ignored on 32-wide boards. Defaults to true when on a 64-wide board.
"slowdown_scrolling" Bool If your Pi is unable to handle the normal refresh rate while scrolling, this will slow it down.
"debug_enabled" Bool Game and other debug data is written to your console.
"preferred": Options for team and division preference
"teams" Array Pass an array of preferred teams. The first team in the list will be used as your 'favorite' team. Example: ["Cubs", "Brewers"]
"divisions" Array Pass an array of preferred divisions that will be rotated through in the order they are entered. Example: ["NL Central", "AL Central"]
"standings": Options for displaying standings for a division
"always_display" Bool Display standings for the provided preferred_divisions.
"mlb_offday" Bool Display standings for the provided preferred_divisions when there are no games on the current day.
"team_offday" Bool Display standings for the provided preferred_divisions when the preferred_teams is not playing on the current day.
"rotation": Options for rotation through the day's games
"enabled" Bool Rotate through each game of the day every 15 seconds.
"scroll_until_finished" Bool If scrolling text takes longer than the rotation rate, wait to rotate until scrolling is done.
"only_preferred" Bool Only rotate through games in your preferred_teams list.
"rates" Dict Dictionary of Floats. Each type of screen can use a different rotation rate. Valid types: "live", "pregame", "final".
Float A Float can be used to set all screen types to the same rotate rate.
"while_preferred_team_live": Options for rotating while your chosen preferred_teams is live
"enabled" Bool Rotation is enabled while your configured preferred_teams game is live.
"during_inning_breaks" Bool Rotation is enabled while your configured preferred_teams game is live during an inning break.
"end_of_day" String A 24-hour time you wish to consider the end of the previous day before starting to display the current day's games. Uses local time from your pi.
"full_team_names" Bool If true and on a 64-wide board, displays the full team name on the scoreboard instead of their abbreviation. This config option is ignored on 32-wide boards. Defaults to true when on a 64-wide board.
"scrolling_speed" Integer Supports an integer between 0 and 4. Sets how fast the scrolling text scrolls.
"debug" Bool Game and other debug data is written to your console.
```

### Flags
Expand Down
37 changes: 26 additions & 11 deletions config.json.example
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
{
"preferred_team": "Cubs",
"preferred_division": "NL Central",
"display_standings": false,
"display_standings_on_offday": true,
"rotate_games": false,
"rotate_rates": { "live": 15.0, "final": 15.0, "pregame": 15.0 },
"stay_on_live_preferred_team": true,
"scroll_until_finished": true,
"preferred": {
"teams": ["Cubs"],
"divisions": ["NL Central"]
},
"standings": {
"team_offday": false,
"mlb_offday": true,
"always_display": false
},
"rotation": {
"enabled": true,
"scroll_until_finished": true,
"only_preferred": false,
"rates": {
"live": 15.0,
"final": 15.0,
"pregame": 15.0
},
"while_preferred_team_live": {
"enabled": false,
"during_inning_breaks": false
}
},
"end_of_day": "00:00",
"display_full_team_names": true,
"slower_scrolling": false,
"debug_enabled": false
"full_team_names": true,
"scrolling_speed": 2,
"debug": false
}
58 changes: 50 additions & 8 deletions data/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pregame import Pregame
from scoreboard import Scoreboard
from status import Status
from inning import Inning
import urllib
import layout
import mlbgame
Expand Down Expand Up @@ -36,6 +37,7 @@ def __init__(self, config):

# What game do we want to start on?
self.current_game_index = self.game_index_for_preferred_team()
self.current_division_index = 0


#
Expand Down Expand Up @@ -72,7 +74,13 @@ def refresh_games(self):
try:
current_day = self.day
self.set_current_date()
self.games = mlbgame.day(self.year, self.month, self.day)

all_games = mlbgame.day(self.year, self.month, self.day)
if self.config.rotation_only_preferred:
self.games = self.__filter_list_of_games(all_games, self.config.preferred_teams)
else:
self.games = all_games

if current_day != self.day:
self.current_game_index = self.game_index_for_preferred_team()
self.games_refresh_time = time.time()
Expand Down Expand Up @@ -106,9 +114,18 @@ def refresh_overview(self):
time.sleep(NETWORK_RETRY_SLEEP_TIME)

# If we run out of retries, just move on to the next game
if attempts_remaining <= 0 and self.config.rotate_games:
if attempts_remaining <= 0 and self.config.rotation_enabled:
self.advance_to_next_game()

# Will use a network call to fetch the preferred team's game overview
def fetch_preferred_team_overview(self):
if not self.is_offday_for_preferred_team():
urllib.urlcleanup()
game = self.games[self.game_index_for_preferred_team()]
game_overview = mlbgame.overview(game.game_id)
debug.log("Preferred Team's Game Status: {}, {} {}".format(game_overview.status, game_overview.inning_state, game_overview.inning))
return game_overview

def __update_layout_state(self):
self.config.layout.set_state()
if self.overview.status == Status.WARMUP:
Expand All @@ -124,11 +141,23 @@ def __update_layout_state(self):
# Standings

def standings_for_preferred_division(self):
return self.__standings_for(self.config.preferred_division)
return self.__standings_for(self.config.preferred_divisions[0])

def __standings_for(self, division_name):
return next(division for division in self.standings.divisions if division.name == division_name)

def current_standings(self):
return self.__standings_for(self.config.preferred_divisions[self.current_division_index])

def advance_to_next_standings(self):
self.current_division_index = self.__next_division_index()
return self.current_standings()

def __next_division_index(self):
counter = self.current_division_index + 1
if counter >= len(self.config.preferred_divisions):
counter = 0
return counter

#
# Games
Expand All @@ -137,15 +166,29 @@ def current_game(self):
return self.games[self.current_game_index]

def advance_to_next_game(self):
# We only need to check the preferred team's game status if we're
# rotating during mid-innings
if self.config.rotation_preferred_team_live_mid_inning and not self.is_offday_for_preferred_team():
preferred_overview = self.fetch_preferred_team_overview()
if Status.is_live(preferred_overview.status) and not Status.is_inning_break(preferred_overview.inning_state):
self.current_game_index = self.game_index_for_preferred_team()
self.overview = preferred_overview
self.needs_refresh = False
self.__update_layout_state()
self.print_overview_debug()
return self.current_game()
self.current_game_index = self.__next_game_index()
return self.current_game()

def game_index_for_preferred_team(self):
if self.config.preferred_team:
return self.__game_index_for(self.config.preferred_team)
if self.config.preferred_teams:
return self.__game_index_for(self.config.preferred_teams[0])
else:
return 0

def __filter_list_of_games(self, games, teams):
return list(filter(lambda game: set(teams) & set([game.away_team, game.home_team]), games))

def __game_index_for(self, team_name):
game_idx = 0
game_idx = next((i for i, game in enumerate(self.games) if team_name in [game.away_team, game.home_team]), 0)
Expand All @@ -157,13 +200,12 @@ def __next_game_index(self):
counter = 0
return counter


#
# Offdays

def is_offday_for_preferred_team(self):
if self.config.preferred_team:
return not (next((i for i, game in enumerate(self.games) if self.config.preferred_team in [game.away_team, game.home_team]), None))
if self.config.preferred_teams:
return not (next((i for i, game in enumerate(self.games) if self.config.preferred_teams[0] in [game.away_team, game.home_team]), False))
else:
return True

Expand Down
122 changes: 85 additions & 37 deletions data/scoreboard_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,47 @@
import sys
import debug

SCROLLING_SPEEDS = [0.3, 0.2, 0.1, 0.075, 0.05]
DEFAULT_SCROLLING_SPEED = 2
DEFAULT_ROTATE_RATE = 15.0
MINIMUM_ROTATE_RATE = 2.0
DEFAULT_ROTATE_RATES = {"live": DEFAULT_ROTATE_RATE, "final": DEFAULT_ROTATE_RATE, "pregame": DEFAULT_ROTATE_RATE}
DEFAULT_PREFERRED_TEAMS = ["Cubs"]
DEFAULT_PREFERRED_DIVISIONS = ["NL Central"]

class ScoreboardConfig:
def __init__(self, filename, width, height):
json = self.read_json(filename)
self.preferred_team = json.get("preferred_team", "Cubs")
self.preferred_division = json.get("preferred_division", "NL Central")
self.rotate_games = json.get("rotate_games", False)
self.rotate_rates = json.get("rotate_rates", DEFAULT_ROTATE_RATES)
self.stay_on_live_preferred_team = json.get("stay_on_live_preferred_team", True)
self.display_standings = json.get("display_standings", False)
self.display_standings_on_offday = json.get("display_standings_on_offday", True)
self.scroll_until_finished = json.get("scroll_until_finished", True)
self.end_of_day = json.get("end_of_day", "00:00")
self.display_full_team_names = json.get("display_full_team_names", True)
self.slower_scrolling = json.get("slower_scrolling", False)
self.debug_enabled = json.get("debug_enabled", False)
def __init__(self, filename_base, width, height):
json = self.__get_config(filename_base)

# Preferred Teams/Divisions
self.preferred_teams = json["preferred"]["teams"]
self.preferred_divisions = json["preferred"]["divisions"]

# Display Standings
self.standings_team_offday = json["standings"]["team_offday"]
self.standings_mlb_offday = json["standings"]["mlb_offday"]
self.standings_always_display = json["standings"]["always_display"]
self.standings_display_offday = False

# Rotation
self.rotation_enabled = json["rotation"]["enabled"]
self.rotation_scroll_until_finished = json["rotation"]["scroll_until_finished"]
self.rotation_only_preferred = json["rotation"]["only_preferred"]
self.rotation_rates = json["rotation"]["rates"]
self.rotation_preferred_team_live_enabled = json["rotation"]["while_preferred_team_live"]["enabled"]
self.rotation_preferred_team_live_mid_inning = json["rotation"]["while_preferred_team_live"]["during_inning_breaks"]

# Misc config options
self.end_of_day = json["end_of_day"]
self.full_team_names = json["full_team_names"]
self.debug = json["debug"]

# Make sure the scrolling speed setting is in range so we don't crash
try:
self.scrolling_speed = SCROLLING_SPEEDS[json["scrolling_speed"]]
except:
debug.warning("Scrolling speed should be an integer between 0 and 4. Using default value of {}".format(DEFAULT_SCROLLING_SPEED))
self.scrolling_speed = SCROLLING_SPEEDS[DEFAULT_SCROLLING_SPEED]

# Get the layout info
json = self.__get_layout(width, height)
Expand All @@ -36,45 +58,57 @@ def __init__(self, filename, width, height):
json = self.__get_colors("scoreboard")
self.scoreboard_colors = Color(json)

#Check the rotate_rates to make sure it's valid and not silly
# Check the preferred teams and divisions are a list or a string
self.check_preferred_teams()
self.check_preferred_divisions()

#Check the rotation_rates to make sure it's valid and not silly
self.check_rotate_rates()
self.check_display_standings_on_offday()

def check_preferred_teams(self):
if not isinstance(self.preferred_teams, str) and not isinstance(self.preferred_teams, list):
debug.warning("preferred_teams should be an array of team names or a single team name string. Using default preferred_teams, {}".format(DEFAULT_PREFERRED_TEAMS))
self.preferred_teams = DEFAULT_PREFERRED_TEAMS
if isinstance(self.preferred_teams, str):
team = self.preferred_teams
self.preferred_teams = [team]

def check_preferred_divisions(self):
if not isinstance(self.preferred_divisions, str) and not isinstance(self.preferred_divisions, list):
debug.warning("preferred_divisions should be an array of division names or a single division name string. Using default preferred_divisions, {}".format(DEFAULT_PREFERRED_DIVISIONS))
self.preferred_divisions = DEFAULT_PREFERRED_DIVISIONS
if isinstance(self.preferred_divisions, str):
division = self.preferred_divisions
self.preferred_divisions = [division]


def check_rotate_rates(self):
if isinstance(self.rotate_rates, dict) == False:
if isinstance(self.rotation_rates, dict) == False:
try:
rate = float(self.rotate_rates)
self.rotate_rates = {"live": rate, "final": rate, "pregame": rate}
rate = float(self.rotation_rates)
self.rotation_rates = {"live": rate, "final": rate, "pregame": rate}
except:
debug.warning("rotate_rates should be a Dict or Float. Using default value. {}".format(DEFAULT_ROTATE_RATES))
self.rotate_rates = DEFAULT_ROTATE_RATES
debug.warning("rotation_rates should be a Dict or Float. Using default value. {}".format(DEFAULT_ROTATE_RATES))
self.rotation_rates = DEFAULT_ROTATE_RATES

for key, value in list(self.rotate_rates.items()):
for key, value in list(self.rotation_rates.items()):
try:
# Try and cast whatever the user passed into a float
rate = float(value)
self.rotate_rates[key] = rate
self.rotation_rates[key] = rate
except:
# Use the default rotate rate if it fails
debug.warning("Unable to convert rotate_rates[\"{}\"] to a Float. Using default value. ({})".format(key, DEFAULT_ROTATE_RATE))
self.rotate_rates[key] = DEFAULT_ROTATE_RATE
self.rotation_rates[key] = DEFAULT_ROTATE_RATE

if self.rotate_rates[key] < MINIMUM_ROTATE_RATE:
if self.rotation_rates[key] < MINIMUM_ROTATE_RATE:
debug.warning("rotate_rates[\"{}\"] is too low. Please set it greater than {}. Using default value. ({})".format(key, MINIMUM_ROTATE_RATE, DEFAULT_ROTATE_RATE))
self.rotate_rates[key] = DEFAULT_ROTATE_RATE
self.rotation_rates[key] = DEFAULT_ROTATE_RATE

# Setup some nice attributes to make sure they all exist
self.live_rotate_rate = self.rotate_rates.get("live", DEFAULT_ROTATE_RATES["live"])
self.final_rotate_rate = self.rotate_rates.get("final", DEFAULT_ROTATE_RATES["final"])
self.pregame_rotate_rate = self.rotate_rates.get("pregame", DEFAULT_ROTATE_RATES["pregame"])

def check_display_standings_on_offday(self):
if self.display_standings_on_offday == 2 and not self.preferred_team:
self.display_standings_on_offday = True
elif self.display_standings_on_offday == 1:
self.display_standings_on_offday = True
elif self.display_standings_on_offday == 0:
self.display_standings_on_offday = False
self.rotation_rates_live = self.rotation_rates.get("live", DEFAULT_ROTATE_RATES["live"])
self.rotation_rates_final = self.rotation_rates.get("final", DEFAULT_ROTATE_RATES["final"])
self.rotation_rates_pregame = self.rotation_rates.get("pregame", DEFAULT_ROTATE_RATES["pregame"])

def read_json(self, filename):
j = {}
Expand All @@ -83,6 +117,20 @@ def read_json(self, filename):
j = json.load(open(path))
return j

def __get_config(self, base_filename):
filename = "{}.json".format(base_filename)
reference_filename = "{}.example".format(filename)
reference_config = self.read_json(reference_filename)
if not reference_filename:
debug.error("Invalid {} reference config file. Make sure {} exists.".format(base_filename, base_filename))
sys.exit(1)

custom_config = self.read_json(filename)
if custom_config:
new_config = deep_update(reference_config, custom_config)
return new_config
return reference_config

def __get_colors(self, base_filename):
filename = "ledcolors/{}.json".format(base_filename)
reference_filename = "{}.example".format(filename)
Expand Down
7 changes: 7 additions & 0 deletions data/status.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from inning import Inning

class Status:
CANCELLED = 'Cancelled'
COMPLETED_EARLY = 'Completed Early'
Expand Down Expand Up @@ -46,3 +48,8 @@ def is_fresh(status):
comes between In Progress and Final and allows a few minutes to see the final outcome before
the rotation kicks in."""
return status in [Status.IN_PROGRESS, Status.GAME_OVER]

@staticmethod
def is_inning_break(inning_state):
"""Returns whether a game is in an inning break (mid/end). Pass in the inning state."""
return inning_state not in [Inning.TOP, Inning.BOTTOM]
2 changes: 1 addition & 1 deletion debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

def set_debug_status(config):
global debug_enabled
debug_enabled = config.debug_enabled
debug_enabled = config.debug

def __debugprint(text):
print(text)
Expand Down
Loading

0 comments on commit d8c793d

Please sign in to comment.