From 4cac468c01a6062df9f9e6a0a98002a12d277636 Mon Sep 17 00:00:00 2001 From: Mike Cifelli <1836280-mike-cifelli@users.noreply.gitlab.com> Date: Mon, 26 Dec 2022 19:37:50 -0500 Subject: [PATCH] Add garage indicators --- .gitignore | 1 + character_display.py | 28 ++++++++++++++---- chutney.py | 20 ++++++++++++- chutney.sample.cfg | 3 ++ colors.py | 2 ++ garage_display.py | 68 ++++++++++++++++++++++++++++++++++++++++++++ garage_updater.py | 38 +++++++++++++++++++++++++ weather_display.py | 2 +- weather_updater.py | 2 +- 9 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 garage_display.py create mode 100644 garage_updater.py diff --git a/.gitignore b/.gitignore index 18b6291..67a94bc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ __pycache__/ *.swp *.tmp chutney.cfg +garage.json weather.txt diff --git a/character_display.py b/character_display.py index ad51236..3355e4a 100644 --- a/character_display.py +++ b/character_display.py @@ -14,15 +14,15 @@ import colors -BLACK = [0, 0, 0] - class CharacterDisplay: - def __init__(self, unicorn, minX=0, maxX=15): + def __init__(self, unicorn, minX=0, maxX=15, minY=0, maxY=15): self.unicorn = unicorn self.minX = minX self.maxX = maxX + self.minY = minY + self.maxY = maxY self.digits = { '0': self.displayZero, @@ -49,8 +49,8 @@ class CharacterDisplay: self.unicorn.show() - def clearRow(self, y): - for row in range(y, y-5, -1): + def clearRow(self, y, rowHeight=5): + for row in range(y, y-rowHeight, -1): for col in range(self.minX, self.maxX + 1): self.setPixel(col, row, colors.BLACK) @@ -77,6 +77,22 @@ class CharacterDisplay: self.setPixel(x+1, y-4, colors.BLACK) self.unicorn.show() + def displaySquare(self, x, y, outlineColor, fillColor): + self.fullLine(x, y, outlineColor) + self.leftSide(x, y-1, outlineColor) + self.leftSide(x, y-2, outlineColor) + self.fullLine(x, y-3, outlineColor) + self.rightSide(x+1, y, outlineColor) + self.rightSide(x+1, y-1, outlineColor) + self.rightSide(x+1, y-2, outlineColor) + self.rightSide(x+1, y-3, outlineColor) + + self.leftSide(x+1, y-1, fillColor) + self.leftSide(x+1, y-2, fillColor) + self.leftSide(x+2, y-1, fillColor) + self.leftSide(x+2, y-2, fillColor) + self.unicorn.show() + def displayZero(self, x, y, color): self.fullLine(x, y, color) self.bothSides(x, y-1, color) @@ -162,5 +178,5 @@ class CharacterDisplay: self.setPixel(start+2, row, color) def setPixel(self, x, y, color): - if x >= self.minX and x <= self.maxX: + if x >= self.minX and x <= self.maxX and y >= self.minY and y <= self.maxY: self.unicorn.set_pixel(x, y, *color) diff --git a/chutney.py b/chutney.py index 2f7ae4d..e6dbf98 100644 --- a/chutney.py +++ b/chutney.py @@ -1,6 +1,8 @@ import sys from character_display import CharacterDisplay +from garage_display import GarageDisplay +from garage_updater import GarageUpdater from multiprocessing import Event from multiprocessing import Process from signal import signal @@ -18,7 +20,8 @@ except ImportError: unicorn.rotation(180) -WEATHER_UPDATE_INTERVAL_IN_SECONDS = 60 +WEATHER_UPDATE_INTERVAL_IN_SECONDS = 10 +GARAGE_UPDATE_INTERVAL_IN_SECONDS = 10 DISPLAY_UPDATE_INTERVAL_IN_SECONDS = 0.5 CONFIG_FILE = 'chutney.cfg' @@ -31,13 +34,18 @@ def main(): weatherThread = Process(target=weatherLoop, args=[haltEvent]) weatherThread.start() + garageThread = Process(target=garageLoop, args=[haltEvent]) + garageThread.start() + characterDisplay = CharacterDisplay(unicorn) timeDisplay = TimeDisplay(characterDisplay, topRow=15) weatherDisplay = WeatherDisplay(characterDisplay, topRow=9) + garageDisplay = GarageDisplay(characterDisplay, topRow=3) while True: timeDisplay.showTime() weatherDisplay.showWeather() + garageDisplay.showGarageState() sleep(DISPLAY_UPDATE_INTERVAL_IN_SECONDS) @@ -51,6 +59,16 @@ def weatherLoop(haltEvent): weatherUpdater.clearWeather() +def garageLoop(haltEvent): + garageUpdater = GarageUpdater(configFile=CONFIG_FILE) + + while not haltEvent.is_set(): + garageUpdater.updateGarageState() + haltEvent.wait(timeout=GARAGE_UPDATE_INTERVAL_IN_SECONDS) + + garageUpdater.clearGarageState() + + def cleanExit(unicorn, haltEvent): def _exit_(signum, frame): unicorn.off() diff --git a/chutney.sample.cfg b/chutney.sample.cfg index c25e4f3..c187965 100644 --- a/chutney.sample.cfg +++ b/chutney.sample.cfg @@ -4,3 +4,6 @@ user=user pass=password lat=lat lon=lon + +[garage] +uri=uri diff --git a/colors.py b/colors.py index 1304b15..7b7f3a9 100644 --- a/colors.py +++ b/colors.py @@ -1,4 +1,6 @@ BLACK = [0, 0, 0 ] +WHITE = [255, 255, 255] +GRAY = [50, 50, 50 ] RED = [255, 0, 0 ] PINK = [255, 0, 64 ] GREEN = [0, 255, 0 ] diff --git a/garage_display.py b/garage_display.py new file mode 100644 index 0000000..7526ad2 --- /dev/null +++ b/garage_display.py @@ -0,0 +1,68 @@ +import colors +import json + + +class GarageDisplay: + def __init__(self, characterDisplay, topRow): + self.characterDisplay = characterDisplay + self.topRow = topRow + self.westDoorStartColumn = 0 + self.eastDoorStartColumn = 5 + self.currentState = {} + self.closedOutlineColor = colors.RED + self.closedFillColor = colors.GRAY + self.openOutlineColor = colors.WHITE + self.openFillColor = colors.BLUE + self.garageFile = 'garage.json' + + def showGarageState(self): + state = self.getGarageState() + + if state != self.currentState: + self.currentState = state + self.characterDisplay.clearRow(self.topRow, rowHeight=4) + + if self.isGarageStateValid(state): + self.showState(state) + + def getGarageState(self): + try: + with open(self.garageFile) as file: + return json.load(file) + except Exception as e: + print(e, flush=True) + return {'error': True} + + def isGarageStateValid(self, state): + return not 'error' in state + + def showState(self, state): + if state["west-door"] == "closed": + self.characterDisplay.displaySquare( + x=self.westDoorStartColumn, + y=self.topRow, + outlineColor=self.closedOutlineColor, + fillColor=self.closedFillColor + ) + else: + self.characterDisplay.displaySquare( + x=self.westDoorStartColumn, + y=self.topRow, + outlineColor=self.openOutlineColor, + fillColor=self.openFillColor + ) + + if state["east-door"] == "closed": + self.characterDisplay.displaySquare( + x=self.eastDoorStartColumn, + y=self.topRow, + outlineColor=self.closedOutlineColor, + fillColor=self.closedFillColor + ) + else: + self.characterDisplay.displaySquare( + x=self.eastDoorStartColumn, + y=self.topRow, + outlineColor=self.openOutlineColor, + fillColor=self.openFillColor + ) diff --git a/garage_updater.py b/garage_updater.py new file mode 100644 index 0000000..5975c59 --- /dev/null +++ b/garage_updater.py @@ -0,0 +1,38 @@ +import os +import requests + +from configparser import ConfigParser + + +class GarageUpdater: + def __init__(self, configFile): + config = ConfigParser() + config.read(configFile) + + self.uri = config['garage'].get('uri') + self.garageTempFile = 'garage.tmp' + self.garageFile = 'garage.json' + self.persistGarageState('{"error": "init"}') + + def updateGarageState(self): + try: + state = self.getGarageState() + self.persistGarageState(state) + except requests.exceptions.ConnectionError as e: + print(e, flush=True) + self.persistGarageState('{"error": "connection"}') + except requests.exceptions.Timeout as e: + print(e, flush=True) + self.persistGarageState('{"error": "timeout"}') + + def clearGarageState(self): + self.persistGarageState('{"error": "halted"}') + + def getGarageState(self): + return requests.get(self.uri, timeout=3).text + + def persistGarageState(self, state): + with open(self.garageTempFile, 'w') as file: + file.write(state) + + os.replace(self.garageTempFile, self.garageFile) diff --git a/weather_display.py b/weather_display.py index 7039d35..9d0c63f 100644 --- a/weather_display.py +++ b/weather_display.py @@ -9,7 +9,7 @@ class WeatherDisplay: self.oneDigitStartColumn = 13 self.digitWidth = 4 self.currentTemperature = '' - self.color = colors.BLUE + self.color = colors.PINK self.weatherFile = 'weather.txt' def showWeather(self): diff --git a/weather_updater.py b/weather_updater.py index 9abc7ed..8edd402 100644 --- a/weather_updater.py +++ b/weather_updater.py @@ -47,7 +47,7 @@ class WeatherUpdater: return currentWeather[0]['intervals'][0]['values']['temperature'] def getWeather(self): - return requests.get(self.uri, auth=self.auth).json() + return requests.get(self.uri, auth=self.auth, timeout=3).json() def persistTemperature(self, temperature): with open(self.weatherTempFile, 'w') as file: