From 0cc16d97a43dc0d472ef275e6d0ea0c9fd2a82df Mon Sep 17 00:00:00 2001 From: Mike Cifelli <1836280-mike-cifelli@users.noreply.gitlab.com> Date: Fri, 23 Dec 2022 11:26:50 -0500 Subject: [PATCH] Add application code and install script --- .gitignore | 2 + .tool-versions | 1 + character_display.py | 152 +++++++++++++++++++++++++++++++++++++++++++ chutney.py | 37 +++++++++++ chutney.sample.cfg | 3 + chutney.service | 13 ++++ install | 33 ++++++++++ time_display.py | 41 ++++++++++++ 8 files changed, 282 insertions(+) create mode 100644 .gitignore create mode 100644 .tool-versions create mode 100644 character_display.py create mode 100644 chutney.py create mode 100644 chutney.sample.cfg create mode 100644 chutney.service create mode 100755 install create mode 100644 time_display.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7243ae5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.swp diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..47179ff --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +python 3.10.9 diff --git a/character_display.py b/character_display.py new file mode 100644 index 0000000..968e86b --- /dev/null +++ b/character_display.py @@ -0,0 +1,152 @@ +# There are 4 different types of patterns used when generating +# a number that is to be placed in a rectangle 3X5 pixels. Combinations of these +# are used to create a number pattern such as: +# * * * +# * +# * * * +# * * +# * * * + +# 1) * * * Full Row +# 2) * * Both Sides +# 3) * Right Side +# 4) * Left Side + + +class CharacterDisplay: + + def __init__(self, unicorn): + self.unicorn = unicorn + + def fullLine(self, start, row): + for x in range(max(start, 0), start+3): + self.unicorn.set_pixel(x, row, 255, 0, 0) + + def bothSides(self, start, row): + if (start >= 0): + self.unicorn.set_pixel(start, row, 255, 0, 0) + + self.unicorn.set_pixel(start+2, row, 255, 0, 0) + + def leftSide(self, start, row): + if (start >= 0): + self.unicorn.set_pixel(start, row, 255, 0, 0) + + def rightSide(self, start, row): + self.unicorn.set_pixel(start+2, row, 255, 0, 0) + + def displayZero(self, x, y): + self.clearNumberPixels(x, y) + self.fullLine(x, y) + self.bothSides(x, y-1) + self.bothSides(x, y-2) + self.bothSides(x, y-3) + self.fullLine(x, y-4) + + def displayOne(self, x, y): + self.clearNumberPixels(x, y) + self.rightSide(x, y) + self.rightSide(x, y-1) + self.rightSide(x, y-2) + self.rightSide(x, y-3) + self.rightSide(x, y-4) + + def displayTwo(self, x, y): + self.clearNumberPixels(x, y) + self.fullLine(x, y) + self.rightSide(x, y-1) + self.fullLine(x, y-2) + self.leftSide(x, y-3) + self.fullLine(x, y-4) + + def displayThree(self, x, y): + self.clearNumberPixels(x, y) + self.fullLine(x, y) + self.rightSide(x, y-1) + self.fullLine(x, y-2) + self.rightSide(x, y-3) + self.fullLine(x, y-4) + + def displayFour(self, x, y): + self.clearNumberPixels(x, y) + self.bothSides(x, y) + self.bothSides(x, y-1) + self.fullLine(x, y-2) + self.rightSide(x, y-3) + self.rightSide(x, y-4) + + def displayFive(self, x, y): + self.clearNumberPixels(x, y) + self.fullLine(x, y) + self.leftSide(x, y-1) + self.fullLine(x, y-2) + self.rightSide(x, y-3) + self.fullLine(x, y-4) + + def displaySix(self, x, y): + self.clearNumberPixels(x, y) + self.fullLine(x, y) + self.leftSide(x, y-1) + self.fullLine(x, y-2) + self.bothSides(x, y-3) + self.fullLine(x, y-4) + + def displaySeven(self, x, y): + self.clearNumberPixels(x, y) + self.fullLine(x, y) + self.rightSide(x, y-1) + self.rightSide(x, y-2) + self.rightSide(x, y-3) + self.rightSide(x, y-4) + + def displayEight(self, x, y): + self.clearNumberPixels(x, y) + self.fullLine(x, y) + self.bothSides(x, y-1) + self.fullLine(x, y-2) + self.bothSides(x, y-3) + self.fullLine(x, y-4) + + def displayNine(self, x, y): + self.clearNumberPixels(x, y) + self.fullLine(x, y) + self.bothSides(x, y-1) + self.fullLine(x, y-2) + self.rightSide(x, y-3) + self.fullLine(x, y-4) + + def displayNumber(self, x, y, number): + if number == 0: + self.displayZero(x, y) + elif number == 1: + self.displayOne(x, y) + elif number == 2: + self.displayTwo(x, y) + elif number == 3: + self.displayThree(x, y) + elif number == 4: + self.displayFour(x, y) + elif number == 5: + self.displayFive(x, y) + elif number == 6: + self.displaySix(x, y) + elif number == 7: + self.displaySeven(x, y) + elif number == 8: + self.displayEight(x, y) + elif number == 9: + self.displayNine(x, y) + + self.unicorn.show() + + def clearNumberPixels(self, x, y): + for y1 in range(y, y-5, -1): + for x1 in range(max(x, 0), x+3): + self.unicorn.set_pixel(x1, y1, 0, 0, 0) + + self.unicorn.show() + + def displayTimeDots(self, x, y): + self.unicorn.set_pixel(x, y-1, 255, 0, 0) + self.unicorn.set_pixel(x, y-3, 255, 0, 0) + self.unicorn.show() diff --git a/chutney.py b/chutney.py new file mode 100644 index 0000000..897ffb9 --- /dev/null +++ b/chutney.py @@ -0,0 +1,37 @@ +import sys + +from character_display import CharacterDisplay +from signal import signal, SIGTERM +from time import sleep +from time_display import TimeDisplay + +try: + import unicornhathd as unicorn + unicorn.rotation(90) +except ImportError: + from unicorn_hat_sim import unicornhathd as unicorn + unicorn.rotation(180) + + +def cleanExit(unicorn): + def _exit_(signum, frame): + unicorn.off() + sys.exit(0) + + return _exit_ + + +def main(): + signal(SIGTERM, cleanExit(unicorn)) + unicorn.brightness(0.3) + + character_display = CharacterDisplay(unicorn) + timeDisplay = TimeDisplay(character_display, lineTop=15) + + while True: + timeDisplay.showTime() + sleep(1) + + +if __name__ == '__main__': + main() diff --git a/chutney.sample.cfg b/chutney.sample.cfg new file mode 100644 index 0000000..570f712 --- /dev/null +++ b/chutney.sample.cfg @@ -0,0 +1,3 @@ +[DEFAULT] +weather_user=user +weather_pass=password diff --git a/chutney.service b/chutney.service new file mode 100644 index 0000000..dd6d6ae --- /dev/null +++ b/chutney.service @@ -0,0 +1,13 @@ +[Unit] +Description=Chutney +After=multi.user.target + +[Service] +Type=simple +WorkingDirectory=$workingDirectory +ExecStart=$execStart +Restart=on-failure +SyslogIdentifier=chutney + +[Install] +WantedBy=multi-user.target diff --git a/install b/install new file mode 100755 index 0000000..cb9995a --- /dev/null +++ b/install @@ -0,0 +1,33 @@ +#! /usr/bin/env python3 + +import os +import sys + +from string import Template +from subprocess import check_call + +EXEC = 'chutney.py' +SERVICE = 'chutney.service' +SYSTEM_DIR = '/etc/systemd/system' + +CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) +SERVICE_TEMPLATE = os.path.join(CURRENT_DIR, SERVICE) +SERVICE_FILE = os.path.join(SYSTEM_DIR, SERVICE) +PYTHON = sys.executable +EXEC_START = f'{PYTHON} {EXEC}' + +with open(SERVICE_TEMPLATE) as f: + serviceTemplate = Template(f.read()) + +serviceFile = serviceTemplate.substitute( + workingDirectory=CURRENT_DIR, + execStart=EXEC_START +) + +with open(SERVICE_FILE, 'w') as f: + f.write(serviceFile) + +check_call(['systemctl', 'daemon-reload']) +check_call(['systemctl', 'enable', '--no-pager', SERVICE]) +check_call(['systemctl', 'restart', '--no-pager', SERVICE]) +check_call(['systemctl', 'status', '--no-pager', SERVICE]) diff --git a/time_display.py b/time_display.py new file mode 100644 index 0000000..39d6123 --- /dev/null +++ b/time_display.py @@ -0,0 +1,41 @@ +from datetime import datetime + + +class TimeDisplay: + + def __init__(self, character_display, lineTop): + self.displayedHourParts = [-1, -1] + self.displayedMinuteParts = [-1, -1] + self.character_display = character_display + self.lineTop = lineTop + + self.character_display.displayTimeDots(7, self.lineTop) + + def showTime(self): + hourParts = self.getTimeParts('%l') + minuteParts = self.getTimeParts('%M') + + if hourParts[0] != self.displayedHourParts[0]: + self.displayedHourParts[0] = hourParts[0] + + if hourParts[0] == 0: + self.character_display.clearNumberPixels(-1, self.lineTop) + else: + self.character_display.displayNumber(-1, self.lineTop, hourParts[0]) + + if hourParts[1] != self.displayedHourParts[1]: + self.displayedHourParts[1] = hourParts[1] + self.character_display.displayNumber(3, self.lineTop, hourParts[1]) + + if minuteParts[0] != self.displayedMinuteParts[0]: + self.displayedMinuteParts[0] = minuteParts[0] + self.character_display.displayNumber(9, self.lineTop, minuteParts[0]) + + if minuteParts[1] != self.displayedMinuteParts[1]: + self.displayedMinuteParts[1] = minuteParts[1] + self.character_display.displayNumber(13, self.lineTop, minuteParts[1]) + + def getTimeParts(self, timePart): + parts = datetime.now().strftime(timePart).strip().rjust(2, "0") + + return [int(x) for x in parts]