diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a73a41b..615aafb0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1,3 @@ { + "python.pythonPath": "/usr/bin/python3" } \ No newline at end of file diff --git a/nhl_setup b/nhl_setup index b1d81de8..2f898801 100755 Binary files a/nhl_setup and b/nhl_setup differ diff --git a/src/api/weather/nwsAlerts.py b/src/api/weather/nwsAlerts.py index 98dd7ae9..57ab99db 100644 --- a/src/api/weather/nwsAlerts.py +++ b/src/api/weather/nwsAlerts.py @@ -95,7 +95,7 @@ def getAlerts(self): # urgency (Immediate, Expected, Future, Unknown) # severity severity level(minor, moderate, severe, extreme) # - if self.data.config.wx_alert_nws_show_expire: + if self.data.config.wxalert_nws_show_expire and _attributes['expires'] != None: warn_date = _attributes['expires'] else: warn_date = _attributes['effective'] diff --git a/src/api/weather/owmWeather.py b/src/api/weather/owmWeather.py index 85fbad02..9635c7c3 100644 --- a/src/api/weather/owmWeather.py +++ b/src/api/weather/owmWeather.py @@ -7,13 +7,14 @@ class owmWxWorker(object): def __init__(self, data, scheduler): - + self.data = data self.weather_frequency = data.config.weather_update_freq self.time_format = data.config.time_format self.icons = get_icons("ecIcons_utf8.csv") self.apikey = data.config.weather_owm_apikey self.network_issues = False + self.owm = OWM(self.apikey) self.owm_manager = self.owm.weather_manager() @@ -24,7 +25,6 @@ def __init__(self, data, scheduler): #Get initial obs self.getWeather() - def getWeather(self): if self.data.config.weather_units == "metric": diff --git a/src/api/weather/wxForecast.py b/src/api/weather/wxForecast.py index 8c2c4249..fe8f90e2 100644 --- a/src/api/weather/wxForecast.py +++ b/src/api/weather/wxForecast.py @@ -20,13 +20,10 @@ def __init__(self, data, scheduler): self.max_days = data.config.weather_forecast_days - if self.data.config.weather_data_feed.lower() == "owm": - self.owm = OWM(self.apikey) self.owm_manager = self.owm.weather_manager() - # Get forecast for next day, every forecast_update hours hour_update = '*/' + str(self.data.config.weather_forecast_update) @@ -133,7 +130,14 @@ def getForecast(self): debug.info("Refreshing OWM daily weather forecast") #lat = self.data.latlng[0] #lon = self.data.latlng[1] - one_call = self.owm_manager.one_call(lat=self.data.latlng[0],lon=self.data.latlng[1]) + one_call = None + try: + one_call = self.owm_manager.one_call(lat=self.data.latlng[0],lon=self.data.latlng[1]) + except Exception as e: + debug.error("Unable to get OWM data error:{0}".format(e)) + self.data.forecast_updated = False + self.network_issues = True + return index=1 forecast = [] @@ -149,7 +153,7 @@ def getForecast(self): temp_high = one_call.forecast_daily[index].temperature('fahrenheit').get('max', None) temp_low = one_call.forecast_daily[index].temperature('fahrenheit').get('min', None) - #Round high and low temps to two digitrs only (ie 25 and not 25.61) + #Round high and low temps to two digits only (ie 25 and not 25.61) temp_hi = str(round(float(temp_high))) + self.data.wx_units[0] temp_lo = str(round(float(temp_low))) + self.data.wx_units[0] diff --git a/src/boards/boards.py b/src/boards/boards.py index 714b5d83..28f6e92f 100644 --- a/src/boards/boards.py +++ b/src/boards/boards.py @@ -22,7 +22,6 @@ class Boards: def __init__(self): - # self.standings_board = Standings(config, matrix) pass # Board handler for PushButton diff --git a/src/boards/christmas.py b/src/boards/christmas.py index a08346d8..4c02c83b 100644 --- a/src/boards/christmas.py +++ b/src/boards/christmas.py @@ -66,7 +66,7 @@ def xmas_today(self) : debug.info("It's Christmas!") - while self.sleepEvent.is_set(): + while not self.sleepEvent.is_set(): self.matrix.clear() diff --git a/src/boards/clock.py b/src/boards/clock.py index 6cbe20ff..132209d8 100644 --- a/src/boards/clock.py +++ b/src/boards/clock.py @@ -90,7 +90,10 @@ def __init__(self, data, matrix, sleepEvent ,duration=None): display_time = 0 while display_time < self.duration and not self.sleepEvent.is_set(): - self.time = datetime.datetime.now().strftime(self.time_format.replace(":", " ")) + if self.data.config.clock_flash_seconds: + self.time = datetime.datetime.now().strftime(self.time_format.replace(":", " ")) + else: + self.time = datetime.datetime.now().strftime(self.time_format) self.meridiem = datetime.datetime.now().strftime("%P") display_time += 1 self.draw_clock() diff --git a/src/boards/seriesticker.py b/src/boards/seriesticker.py index 5bcf102d..bbfe2a51 100644 --- a/src/boards/seriesticker.py +++ b/src/boards/seriesticker.py @@ -10,6 +10,9 @@ import nhl_api class Seriesticker: + """ + TODO: Take out the Series object and create a list of instence from the refresh_playoff in Data instead. Call the data only once a day. + """ def __init__(self, data, matrix, sleepEvent): self.data = data self.rotation_rate = 5 @@ -30,10 +33,8 @@ def render(self): self.index = 0 self.num_series = len(self.allseries) - for s in self.allseries: + for series in self.allseries: self.matrix.clear() - series = Series(s,self.data) - banner_text = "Stanley Cup" color_banner_bg = (200,200,200) color_banner_text = (0,0,0) diff --git a/src/boards/wxWeather.py b/src/boards/wxWeather.py index 2a286897..3615b8cb 100644 --- a/src/boards/wxWeather.py +++ b/src/boards/wxWeather.py @@ -115,27 +115,33 @@ def WxDrawTemp(self,display_loop): ) # Covert temp and apparent temp to floats to compare - temp_float = float(self.data.wx_current[3][:-1]) - app_temp_float = float(self.data.wx_current[4][:-1]) - - if (temp_float > app_temp_float): - # apparent temperature is colder than temperature, show blue - self.matrix.draw_text_layout( - self.layout.temp_app_lo, - self.data.wx_current[4] - ) - elif (temp_float < app_temp_float): - # apparent temperature is warmer than temperature, show red + if self.data.wx_current[3] == "N/A" or self.data.wx_current[4] == "N/A": self.matrix.draw_text_layout( - self.layout.temp_app_hi, - self.data.wx_current[4] - ) + self.layout.temp_app, + self.data.wx_current[4] + ) else: - # apparent temperature is colder than temperature, show green, same as temp - self.matrix.draw_text_layout( - self.layout.temp_app, - self.data.wx_current[4] - ) + temp_float = float(self.data.wx_current[3][:-1]) + app_temp_float = float(self.data.wx_current[4][:-1]) + + if (temp_float > app_temp_float): + # apparent temperature is colder than temperature, show blue + self.matrix.draw_text_layout( + self.layout.temp_app_lo, + self.data.wx_current[4] + ) + elif (temp_float < app_temp_float): + # apparent temperature is warmer than temperature, show red + self.matrix.draw_text_layout( + self.layout.temp_app_hi, + self.data.wx_current[4] + ) + else: + # apparent temperature is colder than temperature, show green, same as temp + self.matrix.draw_text_layout( + self.layout.temp_app, + self.data.wx_current[4] + ) self.matrix.render() diff --git a/src/data/data.py b/src/data/data.py index f0b2a275..75d0aac5 100644 --- a/src/data/data.py +++ b/src/data/data.py @@ -3,14 +3,15 @@ single one. """ - from datetime import datetime, timedelta from time import sleep import debug import nhl_api from api.covid19.data import Data as covid19_data +from data.playoffs import Series from data.status import Status from utils import get_lat_lng +import data.refresh NETWORK_RETRY_SLEEP_TIME = 0.5 @@ -209,20 +210,9 @@ def _is_new_day(self): # Today's date self.today = self.date() - # Get the status info from the API - self.get_status() - - # Get the teams info - self.teams = self.get_teams() - - # Get favorite team's id - self.pref_teams = self.get_pref_teams_id() - # Reset flag self.all_pref_games_final = False - # Reset and refresh Data - self.refresh_data() return True else: debug.info("It is not a new day") @@ -324,7 +314,6 @@ def get_status(self): self.status = [] sleep(NETWORK_RETRY_SLEEP_TIME) - # # Main game event data @@ -435,6 +424,7 @@ def refresh_playoff(self): TODO: Add a refresh function to the Series object instead and trigger a refresh only at specific time in the renderer.(End of a game, new day) """ + print("hello") attempts_remaining = 5 while attempts_remaining > 0: try: @@ -442,7 +432,7 @@ def refresh_playoff(self): self.playoffs = nhl_api.playoff(self.status.season_id) # Check if there is any rounds avaialable and grab the most recent one available. if self.playoffs.rounds: - self.current_round = self.playoffs.rounds[str(self.playoffs.default_round)] + self.current_round = self.playoffs.rounds[str(2)] self.current_round_name = self.current_round.names.name if self.current_round_name == "Stanley Cup Qualifier": self.current_round_name = "Qualifier" @@ -452,21 +442,29 @@ def refresh_playoff(self): debug.info("defaultround number is : {}".format(self.playoffs.default_round)) try: + self.series = [] + # Grab the series of the current round of playoff. - self.series = self.current_round.series + self.series_list = self.current_round.series # Check if prefered team are part of the current round of playoff - self.pref_series = prioritize_pref_series(filter_list_of_series(self.series, self.pref_teams), self.pref_teams) + self.pref_series = prioritize_pref_series(filter_list_of_series(self.series_list, self.pref_teams), self.pref_teams) # If the user as set to show his favorite teams in the seriesticker if self.config.seriesticker_preferred_teams_only and self.pref_series: - self.series = self.pref_series + self.series_list = self.pref_series + + for s in self.series_list: + print(s) + self.series.append(Series(s,self)) + + self.isPlayoff = True + print(self.isPlayoff) except AttributeError: debug.error("The {} Season playoff has not started yet or is unavailable".format(self.playoffs.season)) + self.isPlayoff = False break - - self.isPlayoff = True break except ValueError as error_message: @@ -506,11 +504,7 @@ def is_nhl_offday(self): return True def refresh_data(self): - """ - This method is used when the software move to the next day or . It reset all the main variables - and re-initialize the overall data. - :return: - """ + debug.log("refresing data") # Flag to determine when to refresh data self.needs_refresh = True @@ -521,13 +515,5 @@ def refresh_data(self): # Parse today's date and see if we should use today or yesterday self.refresh_current_date() - # Update team's data - self.get_teams_info() - # Update games for today self.refresh_games() - - # Update standings - self.refresh_standings() - - diff --git a/src/data/refresh.py b/src/data/refresh.py new file mode 100644 index 00000000..d51b1398 --- /dev/null +++ b/src/data/refresh.py @@ -0,0 +1,19 @@ +# NHL Data refresh module. + +def daily(data): + print('refreshing data') + # Update team's data + data.get_teams_info() + + # Get the teams info + data.teams = data.get_teams() + + # Get favorite team's id + data.pref_teams = data.get_pref_teams_id() + + # Update standings + data.refresh_standings() + + # Fetch the playoff data + data.refresh_playoff() + diff --git a/src/data/scoreboard_config.py b/src/data/scoreboard_config.py index 63b447b6..7a9524f8 100644 --- a/src/data/scoreboard_config.py +++ b/src/data/scoreboard_config.py @@ -12,6 +12,9 @@ class ScoreboardConfig: def __init__(self, filename_base, args, size): json = self.__get_config(filename_base) + self.testing_mode = False + self.testScChampions = False + # Misc config options self.debug = json["debug"] self.loglevel = json["loglevel"] @@ -89,7 +92,7 @@ def __init__(self, filename_base, args, size): self.wxalert_alert_feed = json["boards"]["wxalert"]["alert_feed"] #Allow the weather thread to interrupt the current flow of the display loop and show an alert if it shows up #Similar to how a pushbutton interrupts the flow - self.wxalert_show_alerts = json["boards"]["wxalert"]["show_alerts"] + self.wxalert_show_alerts = json["boards"]["wxalert"]["show_alerts"] #Show expire time instead of effective time of NWS alerts self.wxalert_nws_show_expire = json["boards"]["wxalert"]["nws_show_expire"] # Display on top and bottom bar the severity (for US) and type @@ -159,8 +162,10 @@ def __init__(self, filename_base, args, size): if args.testScChampions != None: self.testScChampions = args.testScChampions - else: - self.testScChampions = False + + if args.testing_mode : + self.testing_mode = True + def read_json(self, filename): # Find and return a json file @@ -188,10 +193,11 @@ def __get_config(self, base_filename, error=None): else: debug.error("Invalid {} config file. Make sure {} exists in config/".format(base_filename, base_filename)) sys.exit(1) - + + if base_filename == "config": # Validate against the config.json - debug.info("Now validating config.json.....") + debug.error("INFO: Validating config.json.....") conffile = "config/config.json" schemafile = "config/config.schema.json" @@ -199,10 +205,10 @@ def __get_config(self, base_filename, error=None): schemapath = get_file(schemafile) (valid,msg) = validateConf(confpath,schemapath) if valid: - debug.info("config.json passes validation") + debug.error("INFO: config.json passes validation") else: - debug.error("config.json fails validation: error: [{0}]".format(msg)) - debug.error("Rerun the nhl_setup app to create a valid config.json") + debug.warning("WARN: config.json fails validation: error: [{0}]".format(msg)) + debug.warning("WARN: Rerun the nhl_setup app to create a valid config.json") sys.exit(1) return reference_config diff --git a/src/main.py b/src/main.py index 25843018..1be4eb22 100644 --- a/src/main.py +++ b/src/main.py @@ -111,7 +111,7 @@ def run(): # if commandArgs.updatecheck: data.UpdateRepo = commandArgs.updaterepo - checkupdate = UpdateChecker(data,scheduler) + checkupdate = UpdateChecker(data,scheduler,commandArgs.ghtoken) if data.config.dimmer_enabled: dimmer = Dimmer(data, matrix,scheduler) diff --git a/src/nhl_setup/nhl_setup.py b/src/nhl_setup/nhl_setup.py index 7b6d029d..41098106 100755 --- a/src/nhl_setup/nhl_setup.py +++ b/src/nhl_setup/nhl_setup.py @@ -15,7 +15,7 @@ from time import sleep -SCRIPT_VERSION = "1.4.0" +SCRIPT_VERSION = "1.4.1" TEAMS = ['Avalanche','Blackhawks','Blues','Blue Jackets','Bruins','Canadiens','Canucks','Capitals','Coyotes','Devils','Ducks','Flames','Flyers', 'Golden Knights','Hurricanes','Islanders','Jets','Kings','Maple Leafs','Lightning','Oilers','Panthers','Penguins','Predators', @@ -26,7 +26,8 @@ STATES = ['off_day','scheduled','intermission','post_game'] #Note: for boards, the covid19 in config is NOT the same name as the covid_19 python function #the boards listed below are what's listed in the config -BOARDS = ['clock','weather','wxalert','scoreticker','seriesticker','standings','covid19','christmas'] +# These are boards that have configuration. If your board does not have any config, you don't need to add it +BOARDS = ['clock','weather','wxalert','scoreticker','seriesticker','standings','covid19'] SBIO = ['pushbutton','dimmer','screensaver'] class Clock24hValidator(Validator): @@ -1493,18 +1494,22 @@ def main(): #Check to see if the user wants to validate an existing config.json against the schema #Only from command line + #Change to check on running app every time, if config is not valid, exit. + + conffile = "{0}/config.json".format(args.confdir) + schemafile = "{0}/config.schema.json".format(args.confdir) + + confpath = get_file(conffile) + schemapath = get_file(schemafile) + print("Now validating config......") + (valid,msg) = validateConf(confpath,schemapath) + if valid: + print("Your config.json passes validation and can be used with nhl led scoreboard",GREEN) + else: + print("Your config.json fails validation: error: [{0}]".format(msg),RED) + sys.exit(0) + if args.check: - conffile = "{0}/config.json".format(args.confdir) - schemafile = "{0}/config.schema.json".format(args.confdir) - - confpath = get_file(conffile) - schemapath = get_file(schemafile) - print("Now validating config......") - (valid,msg) = validateConf(confpath,schemapath) - if valid: - print("Your config.json passes validation and can be used with nhl led scoreboard",GREEN) - else: - print("Your config.json fails validation: error: [{0}]".format(msg),RED) sys.exit(0) #Check to see if there was a team name on the command line, if so, create a new config.json from diff --git a/src/renderer/main.py b/src/renderer/main.py index e8110f64..c4017b36 100755 --- a/src/renderer/main.py +++ b/src/renderer/main.py @@ -5,6 +5,8 @@ from boards.boards import Boards from boards.clock import Clock from boards.stanley_cup_champions import StanleyCupChampions +from boards.seriesticker import Seriesticker +import data.refresh from data.scoreboard import Scoreboard from renderer.scoreboard import ScoreboardRenderer from renderer.goal import GoalRenderer @@ -26,6 +28,15 @@ def __init__(self, matrix, data, sleepEvent): self.alternate_data_counter = 1 def render(self): + if self.data.config.testing_mode: + debug.info("Rendering in Testing Mode") + while True: + Seriesticker(self.data, self.matrix, self.sleepEvent).render() + data.refresh.daily(self.data) + self.sleepEvent.wait(1) + debug.info("Testing Mode Refresh") + + while self.data.network_issues: Clock(self.data, self.matrix, self.sleepEvent, duration=60) self.data.refresh_data() @@ -33,7 +44,7 @@ def render(self): while True: try: debug.info('Rendering...') - self.data.refresh_data() + if self.status.is_offseason(self.data.date()): # Offseason (Show offseason related stuff) debug.info("It's offseason") @@ -55,6 +66,8 @@ def render(self): else: debug.info("Game Day Wooooo") self.__render_game_day() + + self.data.refresh_data() except AttributeError as e: debug.log(f"ERROR WHILE RENDERING: {e}") @@ -68,6 +81,7 @@ def __render_offday(self): debug.log('PING !!! Render off day') if self.data._is_new_day(): debug.info('This is a new day') + data.refresh.daily(self.data) return self.data.refresh_data() self.boards._off_day(self.data, self.matrix,self.sleepEvent) diff --git a/src/sbio/dimmer.py b/src/sbio/dimmer.py index 2811e0e8..b8d8dfa4 100644 --- a/src/sbio/dimmer.py +++ b/src/sbio/dimmer.py @@ -101,7 +101,7 @@ def checkDimmer(self): debug.info("It is day time") self.brightness = self.data.config.dimmer_sunrise_brightness - if currtime > self.nighttime and currtime < self.daytime: + if currtime >= self.nighttime and currtime > self.daytime: debug.info("It is night time") self.brightness = self.data.config.dimmer_sunset_brightness diff --git a/src/update_checker.py b/src/update_checker.py index e37e9e76..966a729b 100644 --- a/src/update_checker.py +++ b/src/update_checker.py @@ -5,9 +5,10 @@ from packaging import version class UpdateChecker(object): - def __init__(self,data,scheduler): + def __init__(self,data,scheduler,ghtoken): self.scheduler = scheduler + self.token = ghtoken self.workingDir = os.getcwd() self.versionFile = os.path.join(self.workingDir,'VERSION') self.data = data @@ -33,18 +34,24 @@ def __init__(self,data,scheduler): def CheckForUpdate(self): debug.info("Checking for new release. {} v{} installed in {}".format(self.data.UpdateRepo,self.version,self.workingDir)) + #Use GITHUB Token to remove rate limit, not required if you are doing a single test per day + os.environ['GITHUB_API_TOKEN'] = self.token + debug.info("Using github api token: {} to check updates".format(self.token)) # Use lastversion to check against github latest release repo, don't look at pre releases - latest_version = lastversion.latest(self.data.UpdateRepo, output_format='version', pre_ok=False) - if latest_version != None: - if latest_version > version.parse(self.version): - debug.info("New release v{} available.".format(latest_version)) - self.data.newUpdate = True - else: - debug.info("No new release.") - self.data.newUpdate = False + try: + latest_version = lastversion.latest(self.data.UpdateRepo, output_format='version', pre_ok=False) + except Exception as e: + debug.error("Unable to get info from GitHub. Error: {}".format(e)) else: - debug.error("Unable to get latest version from github, is it tagged properly?") - + if latest_version != None: + if latest_version > version.parse(self.version): + debug.info("New release v{} available.".format(latest_version)) + self.data.newUpdate = True + else: + debug.info("No new release.") + self.data.newUpdate = False + else: + debug.error("Unable to get latest version from github, is it tagged properly?") nextcheck = self.scheduler.get_job('updatecheck').next_run_time debug.info("Next check for update @ {}".format(nextcheck)) diff --git a/src/utils.py b/src/utils.py index b6e9e36d..f50f22a4 100644 --- a/src/utils.py +++ b/src/utils.py @@ -52,6 +52,8 @@ def split_string(string, num_chars): def args(): parser = argparse.ArgumentParser() + parser.add_argument("--testing-mode", action="store", help="Allow to put use a loop in the renderer to do testing. For Development only") + # Options for the rpi-rgb-led-matrix library parser.add_argument("--led-rows", action="store", help="Display rows. 16 for 16x32, 32 for 32x32. (Default: 32)", default=32, type=int) @@ -93,10 +95,11 @@ def args(): parser.add_argument("--terminal-mode", action="store", help="Run on terminal instead of matrix. (Default: False)", default=False, type=bool) parser.add_argument("--updatecheck", action="store_true", help="Check for updates (Default: False)", default=False) parser.add_argument("--updaterepo", action="store", help="Github repo (Default: riffnshred/nhl-scoreboard)", default="riffnshred/nhl-led-scoreboard", type=str) + parser.add_argument("--ghtoken", action="store", help="Github API token for doing update checks(Default: blank)", default="", type=str) parser.add_argument("--logcolor", action="store_true", help="Display log in color (command line only)") parser.add_argument("--loglevel", action="store", help="log level to display (INFO,WARN,ERROR,CRITICAL,DEBUG)", type=str) parser.add_argument("--testScChampions", action="store", help="A flag to test the stanley cup champions board. Put your team's ID", default=None, type=int) - + return parser.parse_args() @@ -122,6 +125,7 @@ def led_matrix_options(args): except AttributeError: debug.warning("Your compiled RGB Matrix Library is out of date.") debug.warning("The --led-pixel-mapper argument will not work until it is updated.") + if args.led_show_refresh: options.show_refresh_rate = 1