Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 42 additions & 26 deletions data/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from data import status
from data.config.color import Color
from data.config.layout import Layout
from data.paths import *
from data.time_formats import TIME_FORMAT_12H, TIME_FORMAT_24H, os_datetime_format
from utils import deep_update

Expand All @@ -21,8 +22,8 @@


class Config:
def __init__(self, filename_base, width, height):
json = self.__get_config(filename_base)
def __init__(self, config_path, width, height):
json = self.__get_config(config_path)

# Preferred Teams/Divisions
self.preferred_teams = json["preferred"]["teams"]
Expand Down Expand Up @@ -212,33 +213,43 @@ def read_json(self, path):
If file not present return empty data.
Exception if json invalid.
"""
j = {}
if os.path.isfile(path):
j = json.load(open(path))
if not os.path.isfile(path):
return {}

return json.load(open(path))

def __get_config(self, path=None):
if path is None:
path = ROOT_DIRECTORY / "config.json"
else:
debug.info(f"Could not find json file {path}. Skipping.")
return j
path = (CURRENT_DIRECTORY / path).with_suffix(".json")
reference_filename = "config.example.json"
reference_path = ROOT_DIRECTORY / reference_filename
reference_config = self.read_json(reference_path)
custom_config = self.read_json(path)
if not reference_config:
debug.critical(f"""\
Invalid example configuration. Make sure {reference_filename} exists in root directory.
You should not edit or move this file!
"""
)
sys.exit(1)

# example config is a "base config" which always gets read.
# our "custom" config contains overrides.
def __get_config(self, base_filename):
filename = "{}.json".format(base_filename)
reference_filename = "config.example.json" # always use this filename.
reference_config = self.read_json(reference_filename)
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_prefix = "colors/{}".format(base_filename)
filename = "{}.json".format(filename_prefix)
reference_filename = "{}.example.json".format(filename_prefix)
reference_colors = self.read_json(reference_filename)
filename = "{}.json".format(base_filename)
reference_filename = "{}.example.json".format(base_filename)
reference_path = COLORS_DIRECTORY / reference_filename
reference_colors = self.read_json(reference_path)
if not reference_colors:
debug.error(
"Invalid {} reference color file. Make sure {} exists in colors/".format(base_filename, base_filename)
debug.critical(f"""\
Invalid reference color file. Make sure {reference_filename} exists in colors/.
You should not edit or move this file!"
"""
)
sys.exit(1)

Expand All @@ -250,15 +261,20 @@ def __get_colors(self, base_filename):
return reference_colors

def __get_layout(self, width, height):
filename_prefix = "coordinates/w{}h{}".format(width, height)
filename_prefix = "w{}h{}".format(width, height)
filename = "{}.json".format(filename_prefix)
reference_filename = "{}.example.json".format(filename_prefix)
reference_layout = self.read_json(reference_filename)
reference_path = COORDINATES_DIRECTORY / reference_filename
reference_layout = self.read_json(reference_path)
if not reference_layout:
# Unsupported coordinates
debug.error(
"Invalid matrix dimensions provided. See top of README for supported dimensions."
"\nIf you would like to see new dimensions supported, please file an issue on GitHub!"
supported_dimensions = sorted([file.name.split(".")[0] for file in COORDINATES_DIRECTORY.glob("*.example.json")], reverse=True)
debug.critical(f"""\
Invalid reference layout file. Make sure {reference_filename} exists in coordinates/
You should not edit or move this file!

Supported dimensions are: {', '.join(supported_dimensions)}
If you aren't sure why you're seeing this, there might not be official support for your matrix dimensions yet.
"""
)
sys.exit(1)

Expand Down
7 changes: 7 additions & 0 deletions data/paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from pathlib import Path

"""Centralized path constants for locating project directories at runtime."""
CURRENT_DIRECTORY = Path.cwd()
ROOT_DIRECTORY = (Path(__file__) / ".." / "..").resolve()
COORDINATES_DIRECTORY = ROOT_DIRECTORY / "coordinates"
COLORS_DIRECTORY = ROOT_DIRECTORY / "colors"
32 changes: 21 additions & 11 deletions data/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,14 @@ def update(self, force=False) -> UpdateStatus:
except pyowm.commons.exceptions.APIRequestError:
debug.warning("[WEATHER] Fetching weather information failed from a connection issue.")
debug.exception("[WEATHER] Error Message:")
# Set some placeholder weather info if this is our first weather update
if self.temp is None:
self.temp = -99
if self.wind_speed is None:
self.wind_speed = -9
if self.wind_dir is None:
self.wind_dir = 0
if self.conditions is None:
self.conditions = "Error"
if self.icon_name is None:
self.icon_name = "50d"
self.__set_fail_state_defaults()
return UpdateStatus.FAIL
except pyowm.commons.exceptions.NotFoundError:
debug.warning("[WEATHER] Fetching weather information failed from a not found error.")
debug.warning("[WEATHER] The 'weather.location' config is likely not formatted correctly.")
debug.warning("[WEATHER] The correct format is '<city>,<state>,<country>' (example: 'Chicago,il,us')")
debug.exception("[WEATHER] Error Message:")
self.__set_fail_state_defaults()
return UpdateStatus.FAIL

return UpdateStatus.DEFERRED
Expand Down Expand Up @@ -113,3 +110,16 @@ def __deg_to_compass(self, degrees):
val = int((degrees / 22.5) + 0.5)
arr = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"]
return arr[(val % 16)]

def __set_fail_state_defaults(self):
# Set some placeholder weather info if this is our first weather update
if self.temp is None:
self.temp = -99
if self.wind_speed is None:
self.wind_speed = -9
if self.wind_dir is None:
self.wind_dir = 0
if self.conditions is None:
self.conditions = "Error"
if self.icon_name is None:
self.icon_name = "50d"
1 change: 1 addition & 0 deletions debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@
info = logger.info
warning = logger.warning
error = logger.error
critical = logger.critical
exception = logger.exception
7 changes: 3 additions & 4 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ def __render_main(matrix, data):

if __name__ == "__main__":
# Check for led configuration arguments
command_line_args = args()
matrixOptions = led_matrix_options(command_line_args)
clargs = args()
matrixOptions = led_matrix_options(clargs)

if driver.is_emulated():
matrixOptions.emulator_title = f"{SCRIPT_NAME} v{SCRIPT_VERSION}"
Expand All @@ -167,8 +167,7 @@ def __render_main(matrix, data):
# Initialize the matrix
matrix = RGBMatrix(options=matrixOptions)
try:
config, _ = os.path.splitext(command_line_args.config)
main(matrix, config)
main(matrix, clargs.config)
except:
debug.exception("Untrapped error in main!")
sys.exit(1)
Expand Down
6 changes: 3 additions & 3 deletions tests/test_validate_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ def test_custom_config_files(self):
self.assertEqual(
custom_config_files(),
[
(ROOT_DIR, "config.json", { "ignored_keys": [], "renamed_keys": {"preferred_game_update_delay_in_10s_of_seconds": "preferred_game_delay_multiplier"} }),
(COORDINATES_DIR, "config.json", { "ignored_keys": COORDINATES_IGNORED_KEYS, "renamed_keys": {} }),
(COLORS_DIR, "config.json", { "ignored_keys": COLORS_IGNORED_KEYS, "renamed_keys": {} }),
(ROOT_DIRECTORY, "config.json", { "ignored_keys": [], "renamed_keys": {"preferred_game_update_delay_in_10s_of_seconds": "preferred_game_delay_multiplier"} }),
(COORDINATES_DIRECTORY, "config.json", { "ignored_keys": COORDINATES_IGNORED_KEYS, "renamed_keys": {} }),
(COLORS_DIRECTORY, "config.json", { "ignored_keys": COLORS_IGNORED_KEYS, "renamed_keys": {} }),
]
)

Expand Down
4 changes: 2 additions & 2 deletions utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ def args():
parser.add_argument(
"--config",
action="store",
help="Base file name for config file. Can use relative path, e.g. config/rockies.config",
default="config",
help="Relative path to a custom config JSON file. Defaults to root config.json. The '.json' suffix is optional.",
default=None,
type=str,
)
parser.add_argument(
Expand Down
10 changes: 4 additions & 6 deletions validate_config.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import copy, json, os

ROOT_DIR = "."
COORDINATES_DIR = os.path.join(ROOT_DIR, "coordinates")
COLORS_DIR = os.path.join(ROOT_DIR, "colors")
from data.paths import *

VALIDATIONS = {
ROOT_DIR: {
ROOT_DIRECTORY: {
"ignored_keys": [],
"renamed_keys": {
"preferred_game_update_delay_in_10s_of_seconds": "preferred_game_delay_multiplier",
},
},
COORDINATES_DIR: {
COORDINATES_DIRECTORY: {
"ignored_keys": [
"font_name",
"no_hitter",
Expand All @@ -20,7 +18,7 @@
],
"renamed_keys": {},
},
COLORS_DIR: {
COLORS_DIRECTORY: {
"ignored_keys": [
"city_connect"
],
Expand Down
Loading