From fc77feedb204c4d30fccaea3510bd7a9594011b4 Mon Sep 17 00:00:00 2001 From: Mike Cifelli <1836280-mike-cifelli@users.noreply.gitlab.com> Date: Sun, 25 Dec 2022 11:01:48 -0500 Subject: [PATCH] Run weather updates in a separate thread --- .gitignore | 1 + chutney.py | 38 +++++++++++++++++++++----------- time_display.py | 16 ++++++++------ weather_display.py | 30 +++++++++++--------------- weather_updater.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 37 deletions(-) create mode 100644 weather_updater.py diff --git a/.gitignore b/.gitignore index 19bfbb7..0e5b2d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__/ *.swp chutney.cfg +weather.txt diff --git a/chutney.py b/chutney.py index bf36b94..cc66115 100644 --- a/chutney.py +++ b/chutney.py @@ -1,10 +1,12 @@ import sys +import threading from character_display import CharacterDisplay from signal import signal, SIGTERM from time import sleep from time_display import TimeDisplay from weather_display import WeatherDisplay +from weather_updater import WeatherUpdater try: import unicornhathd as unicorn @@ -14,34 +16,44 @@ except ImportError: unicorn.rotation(180) -def cleanExit(unicorn): +WEATHER_UPDATE_INTERVAL_IN_SECONDS = 10 +DISPLAY_UPDATE_INTERVAL_IN_SECONDS = 0.5 +CONFIG_FILE = 'chutney.cfg' + + +def cleanExit(unicorn, haltEvent): def _exit_(signum, frame): unicorn.off() + haltEvent.set() sys.exit(0) return _exit_ +def weatherLoop(haltEvent): + weatherUpdater = WeatherUpdater(configFile=CONFIG_FILE) + + while not haltEvent.is_set(): + weatherUpdater.updateWeather() + sleep(WEATHER_UPDATE_INTERVAL_IN_SECONDS) + + def main(): - signal(SIGTERM, cleanExit(unicorn)) + haltEvent = threading.Event() + signal(SIGTERM, cleanExit(unicorn, haltEvent)) unicorn.brightness(0.3) + weatherThread = threading.Thread(target=weatherLoop, args=[haltEvent]) + weatherThread.start() + characterDisplay = CharacterDisplay(unicorn) timeDisplay = TimeDisplay(characterDisplay, topRow=15) - weatherDisplay = WeatherDisplay(characterDisplay, topRow=9, configFile='chutney.cfg') - - x = 0 + weatherDisplay = WeatherDisplay(characterDisplay, topRow=9) while True: timeDisplay.showTime() - - if (x == 0): - weatherDisplay.showWeather() - - x = x + 1 - x = x % 120 - - sleep(1) + weatherDisplay.showWeather() + sleep(DISPLAY_UPDATE_INTERVAL_IN_SECONDS) if __name__ == '__main__': diff --git a/time_display.py b/time_display.py index c0c4d53..1f939fd 100644 --- a/time_display.py +++ b/time_display.py @@ -16,15 +16,11 @@ class TimeDisplay: self.timeDotsColumn = 7 self.minuteStartColumn = 9 - self.characterDisplay.displayTimeDots( - x=self.timeDotsColumn, - y=self.topRow, - color=self.color - ) - def showTime(self): self.showHour(self.getHourDigits()) + self.showTimeDots() self.showMinute(self.getMinuteDigits()) + self.currentColor = self.color def showHour(self, hour): if hour[0] != self.currentHour[0]: @@ -39,6 +35,14 @@ class TimeDisplay: self.currentHour[1] = hour[1] self.showDigit(self.hourStartColumn + 4, hour[1]) + def showTimeDots(self): + if self.color != self.currentColor: + self.characterDisplay.displayTimeDots( + x=self.timeDotsColumn, + y=self.topRow, + color=self.color + ) + def showMinute(self, minute): if minute[0] != self.currentMinute[0]: self.currentMinute[0] = minute[0] diff --git a/weather_display.py b/weather_display.py index 5e93b58..03dcbbc 100644 --- a/weather_display.py +++ b/weather_display.py @@ -1,37 +1,24 @@ import colors import requests -from configparser import ConfigParser - class WeatherDisplay: - def __init__(self, characterDisplay, topRow, configFile): + def __init__(self, characterDisplay, topRow): self.characterDisplay = characterDisplay self.topRow = topRow self.currentTemperature = [9, 9] self.color = colors.BLUE - - config = ConfigParser() - config.read(configFile) - - host = config['weather'].get('host') - user = config['weather'].get('user') - password = config['weather'].get('pass') - lat = config['weather'].get('lat') - lon = config['weather'].get('lon') - - self.uri = f'https://{host}/api/weather?lat={lat}&lon={lon}&units=metric' - self.auth = (user, password) + self.weatherFile = 'weather.txt' def showWeather(self): try: self.showTemperature( self.getTemperatureCharacters(self.getTemperature())) except requests.exceptions.ConnectionError: - print("connection error", flush=True) + print('connection error', flush=True) self.characterDisplay.clearRow(self.topRow) except requests.exceptions.Timeout: - print("timeout", flush=True) + print('timeout', flush=True) self.characterDisplay.clearRow(self.topRow) def showTemperature(self, temperature): @@ -53,7 +40,14 @@ class WeatherDisplay: self.currentTemperature = temperature def getTemperature(self): - return list(filter(lambda t: t["timestep"] == 'current', requests.get(self.uri, auth=self.auth, timeout=2).json()["tomorrow"]["data"]['timelines']))[0]["intervals"][0]["values"]["temperature"] + # TODO - don't use a file left around from a previous run + # TODO - clear temp on errors from updater + try: + with open(self.weatherFile) as file: + return int(file.readline().strip()) + except Exception as e: + print(e) + return 0 def getTemperatureCharacters(self, temperature): return list(str(round(temperature))) diff --git a/weather_updater.py b/weather_updater.py new file mode 100644 index 0000000..21f83d9 --- /dev/null +++ b/weather_updater.py @@ -0,0 +1,54 @@ +import os +import requests + +from configparser import ConfigParser + + +class WeatherUpdater: + def __init__(self, configFile): + config = ConfigParser() + config.read(configFile) + + host = config['weather'].get('host') + user = config['weather'].get('user') + password = config['weather'].get('pass') + lat = config['weather'].get('lat') + lon = config['weather'].get('lon') + + self.uri = f'https://{host}/api/weather?lat={lat}&lon={lon}&units=metric' + self.auth = (user, password) + self.weatherTempFile = 'weather.tmp' + self.weatherFile = 'weather.txt' + + # TODO - clear weather file on init and on failure + + def updateWeather(self): + try: + temperature = round(self.getTemperature()) + self.persistTemperature(temperature) + except requests.exceptions.ConnectionError: + print('connection error', flush=True) + self.characterDisplay.clearRow(self.topRow) + except requests.exceptions.Timeout: + print('timeout', flush=True) + self.characterDisplay.clearRow(self.topRow) + + def getTemperature(self): + weather = self.getWeather() + currentWeather = list( + filter( + lambda t: t['timestep'] == 'current', + weather['tomorrow']['data']['timelines'] + ) + ) + + return currentWeather[0]['intervals'][0]['values']['temperature'] + + def getWeather(self): + return requests.get(self.uri, auth=self.auth).json() + + def persistTemperature(self, temperature): + with open(self.weatherTempFile, 'w') as file: + file.write(str(temperature)) + + os.replace(self.weatherTempFile, self.weatherFile)