From 6bc9847a518848a2f0050126c649b25f59a1a860 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Mon, 19 Apr 2021 21:42:33 +0100 Subject: [PATCH 1/8] Add kiln-display --- kiln-display.py | 137 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 kiln-display.py diff --git a/kiln-display.py b/kiln-display.py new file mode 100644 index 00000000..3ca26be8 --- /dev/null +++ b/kiln-display.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python + +import websocket +import json +import time +import datetime +import digitalio +import board +import adafruit_rgb_display.st7789 as st7789 +from PIL import Image, ImageDraw, ImageFont + + +KILN_HOSTNAME = 'localhost:8081' +MIN_UPDATE_SECS = 10 + +status_ws = websocket.WebSocket() +storage_ws = websocket.WebSocket() + +# Configuration for CS and DC pins for Raspberry Pi +cs_pin = digitalio.DigitalInOut(board.CE0) +dc_pin = digitalio.DigitalInOut(board.D25) +reset_pin = None +BAUDRATE = 64000000 # The pi can be very fast! +# Create the ST7789 display: +display = st7789.ST7789( + board.SPI(), + cs=cs_pin, + dc=dc_pin, + rst=reset_pin, + baudrate=BAUDRATE, + height=240, y_offset=80, rotation=180 +) +display.fill() + +# turn backlight on +backlight = digitalio.DigitalInOut(board.D22) +backlight.switch_to_output() +backlight.value = True + +# create screen and font +screen = Image.new("RGB", (display.width, display.height), (0, 0, 0)) +screend = ImageDraw.Draw(screen) +screenfont = ImageFont.truetype("/home/adq/DroidSans.ttf", 46) +chartminx = 0 +chartw = display.width +chartminy = int(display.height / 2) +charth = int(display.height / 2) + +# main loop +cur_profile = None +last_update = datetime.datetime.now() +while True: + # gather data from kiln controller. + try: + msg = json.loads(status_ws.recv()) + if msg.get('profile') and not cur_profile: + storage_ws.send('GET') + for profile in json.loads(storage_ws.recv()): + if profile['name'] == msg.get('profile'): + cur_profile = profile + break + + elif not msg.get('profile'): + cur_profile = None + + except websocket.WebSocketException: + try: + status_ws.connect(f'ws://{KILN_HOSTNAME}/status') + storage_ws.connect(f'ws://{KILN_HOSTNAME}/storage') + except Exception: + time.sleep(5) + + continue + + # we don't need to update ALL the time + if (datetime.datetime.now() - last_update).total_seconds() < MIN_UPDATE_SECS: + continue + last_update = datetime.datetime.now() + + # setup the basic display + screend.rectangle([0, 0, display.width, display.height], fill='black') + screend.line([chartminx, chartminy, chartminx + chartw, chartminy], fill='white') + + # show the current temperature + if msg.get('temperature'): + temp = int(msg['temperature']) + text = f"{temp}°" + (tw, th) = screenfont.getsize(text) + screend.text((0, display.height - th), text, font=screenfont, fill='yellow') + + # inform if we're actively heating + if msg.get('heat'): + screend.ellipse((display.width - 25, display.height - 25, display.width - 5, display.height - 5), fill='red') + + # if we have a profile, show details of that! + if cur_profile: + cur_profile_data = cur_profile['data'] + + # compute ranges of data + mintime = min([i[0] for i in cur_profile_data]) + maxtime = max([i[0] for i in cur_profile_data]) + timerange = maxtime - mintime + mintemp = 0 + maxtemp = max([i[1] for i in cur_profile_data]) + temprange = maxtemp - mintemp + + # draw chart of the temperature profie + line = [] + for i in sorted(cur_profile_data, key=lambda x: x[0]): + x = chartminx + (((i[0] - mintime) * chartw) / timerange) + y = chartminy - (((i[1] - mintemp) * charth) / temprange) + line.extend([x, y]) + screend.line(line, fill='yellow') + + # draw current position as a blue line + cur_time = msg['runtime'] if msg['runtime'] > 0 else 0 + cur_time_x = ((cur_time - mintime) * chartw) / timerange + cur_temp = int(msg['temperature']) + cur_temp_y = ((cur_temp - mintemp) * charth) / temprange + screend.line([chartminx + cur_time_x, chartminy, chartminx + cur_time_x, chartminy - charth], fill='blue') + + # show the where we are + time_done = msg['runtime'] if msg['runtime'] > 0 else 0 + time_done_mins = int((time_done / 60) % 60) + time_done_hours = int(time_done / 60 / 60) + screend.text((0, chartminy), f"{time_done_hours:02d}:{time_done_mins:02d}", font=screenfont, fill='blue') + + # show how long we have left + time_left = msg['totaltime'] - msg['runtime'] + time_left_mins = int((time_left / 60) % 60) + time_left_hours = int((time_left / 60) / 60) + text = f"{time_left_hours:02d}:{time_left_mins:02d}" + (tw, th) = screenfont.getsize(text) + screend.text((display.width - tw, chartminy), text, font=screenfont, fill='white') + + # update display + display.image(screen) From 013d475f1beb78f0d537fb978bd2493b946317ea Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Mon, 19 Apr 2021 22:05:14 +0100 Subject: [PATCH 2/8] Add requirements for display --- requirements-kiln-display.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 requirements-kiln-display.txt diff --git a/requirements-kiln-display.txt b/requirements-kiln-display.txt new file mode 100644 index 00000000..2611ff86 --- /dev/null +++ b/requirements-kiln-display.txt @@ -0,0 +1,4 @@ +websocket-client +Pillow +adafruit-circuitpython-rgb-display +numpy From a679e3c55de6a3ce7d4fae45b6f85eb799cd5397 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Mon, 19 Apr 2021 22:07:31 +0100 Subject: [PATCH 3/8] Nice controller process --- lib/init/kiln-controller.service | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/init/kiln-controller.service b/lib/init/kiln-controller.service index 318e4c05..e8cb3efd 100644 --- a/lib/init/kiln-controller.service +++ b/lib/init/kiln-controller.service @@ -2,6 +2,7 @@ Description=kiln-controller [Service] +Nice=-20 ExecStart=/home/pi/kiln-controller/venv/bin/python /home/pi/kiln-controller/kiln-controller.py [Install] From c2303e4f7cd6debb1d84b587cea89d0d9341974f Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Mon, 19 Apr 2021 22:07:42 +0100 Subject: [PATCH 4/8] Add kiln display systemd script --- lib/init/kiln-display.service | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 lib/init/kiln-display.service diff --git a/lib/init/kiln-display.service b/lib/init/kiln-display.service new file mode 100644 index 00000000..87fd412e --- /dev/null +++ b/lib/init/kiln-display.service @@ -0,0 +1,9 @@ +[Unit] +Description=kiln-display + +[Service] +Nice=10 +ExecStart=/home/pi/kiln-controller/venv/bin/python /home/pi/kiln-controller/kiln-display.py + +[Install] +WantedBy=multi-user.target From 920c1bd03ed8c27fb8f89009603bd26c762a48d7 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Thu, 29 Apr 2021 23:33:28 +0100 Subject: [PATCH 5/8] argparse goodness --- kiln-display.py | 258 +++++++++++++++++++++++++----------------------- 1 file changed, 134 insertions(+), 124 deletions(-) diff --git a/kiln-display.py b/kiln-display.py index 3ca26be8..86d5dbe1 100644 --- a/kiln-display.py +++ b/kiln-display.py @@ -4,134 +4,144 @@ import json import time import datetime +import argparse import digitalio import board import adafruit_rgb_display.st7789 as st7789 from PIL import Image, ImageDraw, ImageFont -KILN_HOSTNAME = 'localhost:8081' -MIN_UPDATE_SECS = 10 - -status_ws = websocket.WebSocket() -storage_ws = websocket.WebSocket() - -# Configuration for CS and DC pins for Raspberry Pi -cs_pin = digitalio.DigitalInOut(board.CE0) -dc_pin = digitalio.DigitalInOut(board.D25) -reset_pin = None -BAUDRATE = 64000000 # The pi can be very fast! -# Create the ST7789 display: -display = st7789.ST7789( - board.SPI(), - cs=cs_pin, - dc=dc_pin, - rst=reset_pin, - baudrate=BAUDRATE, - height=240, y_offset=80, rotation=180 -) -display.fill() - -# turn backlight on -backlight = digitalio.DigitalInOut(board.D22) -backlight.switch_to_output() -backlight.value = True - -# create screen and font -screen = Image.new("RGB", (display.width, display.height), (0, 0, 0)) -screend = ImageDraw.Draw(screen) -screenfont = ImageFont.truetype("/home/adq/DroidSans.ttf", 46) -chartminx = 0 -chartw = display.width -chartminy = int(display.height / 2) -charth = int(display.height / 2) - -# main loop -cur_profile = None -last_update = datetime.datetime.now() -while True: - # gather data from kiln controller. - try: - msg = json.loads(status_ws.recv()) - if msg.get('profile') and not cur_profile: - storage_ws.send('GET') - for profile in json.loads(storage_ws.recv()): - if profile['name'] == msg.get('profile'): - cur_profile = profile - break - - elif not msg.get('profile'): - cur_profile = None - - except websocket.WebSocketException: - try: - status_ws.connect(f'ws://{KILN_HOSTNAME}/status') - storage_ws.connect(f'ws://{KILN_HOSTNAME}/storage') - except Exception: - time.sleep(5) - - continue - - # we don't need to update ALL the time - if (datetime.datetime.now() - last_update).total_seconds() < MIN_UPDATE_SECS: - continue +UPDATE_MIN_SECS = 10 + + +def display(hostname): + status_ws = websocket.WebSocket() + storage_ws = websocket.WebSocket() + + # Configuration for CS and DC pins for Raspberry Pi + cs_pin = digitalio.DigitalInOut(board.CE0) + dc_pin = digitalio.DigitalInOut(board.D25) + reset_pin = None + BAUDRATE = 64000000 # The pi can be very fast! + # Create the ST7789 display: + display = st7789.ST7789( + board.SPI(), + cs=cs_pin, + dc=dc_pin, + rst=reset_pin, + baudrate=BAUDRATE, + height=240, y_offset=80, rotation=180 + ) + display.fill() + + # turn backlight on + backlight = digitalio.DigitalInOut(board.D22) + backlight.switch_to_output() + backlight.value = True + + # create screen and font + screen = Image.new("RGB", (display.width, display.height), (0, 0, 0)) + screend = ImageDraw.Draw(screen) + screenfont = ImageFont.truetype("/home/adq/DroidSans.ttf", 46) + chartminx = 0 + chartw = display.width + chartminy = int(display.height / 2) + charth = int(display.height / 2) + + # main loop + cur_profile = None last_update = datetime.datetime.now() - - # setup the basic display - screend.rectangle([0, 0, display.width, display.height], fill='black') - screend.line([chartminx, chartminy, chartminx + chartw, chartminy], fill='white') - - # show the current temperature - if msg.get('temperature'): - temp = int(msg['temperature']) - text = f"{temp}°" - (tw, th) = screenfont.getsize(text) - screend.text((0, display.height - th), text, font=screenfont, fill='yellow') - - # inform if we're actively heating - if msg.get('heat'): - screend.ellipse((display.width - 25, display.height - 25, display.width - 5, display.height - 5), fill='red') - - # if we have a profile, show details of that! - if cur_profile: - cur_profile_data = cur_profile['data'] - - # compute ranges of data - mintime = min([i[0] for i in cur_profile_data]) - maxtime = max([i[0] for i in cur_profile_data]) - timerange = maxtime - mintime - mintemp = 0 - maxtemp = max([i[1] for i in cur_profile_data]) - temprange = maxtemp - mintemp - - # draw chart of the temperature profie - line = [] - for i in sorted(cur_profile_data, key=lambda x: x[0]): - x = chartminx + (((i[0] - mintime) * chartw) / timerange) - y = chartminy - (((i[1] - mintemp) * charth) / temprange) - line.extend([x, y]) - screend.line(line, fill='yellow') - - # draw current position as a blue line - cur_time = msg['runtime'] if msg['runtime'] > 0 else 0 - cur_time_x = ((cur_time - mintime) * chartw) / timerange - cur_temp = int(msg['temperature']) - cur_temp_y = ((cur_temp - mintemp) * charth) / temprange - screend.line([chartminx + cur_time_x, chartminy, chartminx + cur_time_x, chartminy - charth], fill='blue') - - # show the where we are - time_done = msg['runtime'] if msg['runtime'] > 0 else 0 - time_done_mins = int((time_done / 60) % 60) - time_done_hours = int(time_done / 60 / 60) - screend.text((0, chartminy), f"{time_done_hours:02d}:{time_done_mins:02d}", font=screenfont, fill='blue') - - # show how long we have left - time_left = msg['totaltime'] - msg['runtime'] - time_left_mins = int((time_left / 60) % 60) - time_left_hours = int((time_left / 60) / 60) - text = f"{time_left_hours:02d}:{time_left_mins:02d}" - (tw, th) = screenfont.getsize(text) - screend.text((display.width - tw, chartminy), text, font=screenfont, fill='white') - - # update display - display.image(screen) + while True: + # gather data from kiln controller. + try: + msg = json.loads(status_ws.recv()) + if msg.get('profile') and not cur_profile: + storage_ws.send('GET') + for profile in json.loads(storage_ws.recv()): + if profile['name'] == msg.get('profile'): + cur_profile = profile + break + + elif not msg.get('profile'): + cur_profile = None + + except websocket.WebSocketException: + try: + status_ws.connect(f'ws://{hostname}/status') + storage_ws.connect(f'ws://{hostname}/storage') + except Exception: + time.sleep(5) + + continue + + # we don't need to update ALL the time + if (datetime.datetime.now() - last_update).total_seconds() < UPDATE_MIN_SECS: + continue + last_update = datetime.datetime.now() + + # setup the basic display + screend.rectangle([0, 0, display.width, display.height], fill='black') + screend.line([chartminx, chartminy, chartminx + chartw, chartminy], fill='white') + + # show the current temperature + if msg.get('temperature'): + temp = int(msg['temperature']) + text = f"{temp}°" + (tw, th) = screenfont.getsize(text) + screend.text((0, display.height - th), text, font=screenfont, fill='yellow') + + # inform if we're actively heating + if msg.get('heat'): + screend.ellipse((display.width - 25, display.height - 25, display.width - 5, display.height - 5), fill='red') + + # if we have a profile, show details of that! + if cur_profile: + cur_profile_data = cur_profile['data'] + + # compute ranges of data + mintime = min([i[0] for i in cur_profile_data]) + maxtime = max([i[0] for i in cur_profile_data]) + timerange = maxtime - mintime + mintemp = 0 + maxtemp = max([i[1] for i in cur_profile_data]) + temprange = maxtemp - mintemp + + # draw chart of the temperature profie + line = [] + for i in sorted(cur_profile_data, key=lambda x: x[0]): + x = chartminx + (((i[0] - mintime) * chartw) / timerange) + y = chartminy - (((i[1] - mintemp) * charth) / temprange) + line.extend([x, y]) + screend.line(line, fill='yellow') + + # draw current position as a blue line + cur_time = msg['runtime'] if msg['runtime'] > 0 else 0 + cur_time_x = ((cur_time - mintime) * chartw) / timerange + cur_temp = int(msg['temperature']) + cur_temp_y = ((cur_temp - mintemp) * charth) / temprange + screend.line([chartminx + cur_time_x, chartminy, chartminx + cur_time_x, chartminy - charth], fill='blue') + + # show the where we are + time_done = msg['runtime'] if msg['runtime'] > 0 else 0 + time_done_mins = int((time_done / 60) % 60) + time_done_hours = int(time_done / 60 / 60) + screend.text((0, chartminy), f"{time_done_hours:02d}:{time_done_mins:02d}", font=screenfont, fill='blue') + + # show how long we have left + time_left = msg['totaltime'] - msg['runtime'] + time_left_mins = int((time_left / 60) % 60) + time_left_hours = int((time_left / 60) / 60) + text = f"{time_left_hours:02d}:{time_left_mins:02d}" + (tw, th) = screenfont.getsize(text) + screend.text((display.width - tw, chartminy), text, font=screenfont, fill='white') + + # update display + display.image(screen) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Log kiln data for analysis.') + parser.add_argument('--hostname', type=str, default="localhost:8081", help="The kiln-controller hostname:port") + args = parser.parse_args() + + display(args.hostname) From 9c52a66b96b7f57cbf4a61d0f239e0dd3b419a63 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Thu, 29 Apr 2021 23:44:31 +0100 Subject: [PATCH 6/8] more params --- kiln-display.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/kiln-display.py b/kiln-display.py index 86d5dbe1..6bde3b5e 100644 --- a/kiln-display.py +++ b/kiln-display.py @@ -10,11 +10,18 @@ import adafruit_rgb_display.st7789 as st7789 from PIL import Image, ImageDraw, ImageFont - -UPDATE_MIN_SECS = 10 - - -def display(hostname): +# This is designed to drive an Adafruit Mini PiTFT 1.3" (https://www.adafruit.com/product/4484) +# +# You will require a copy of DroidSans.ttf in /home/pi +# +# As this occupies the GPIOs currently used as defaults in config.py, you'll have to rewrire your Pi. +# Remember to update config.py with the new ones! +# +# Technically you do not need to install numpy, but it is very much recommended as the +# non-numpy fallback code will consume much CPU. + + +def display(hostname, minupdatesecs, font_ttf): status_ws = websocket.WebSocket() storage_ws = websocket.WebSocket() @@ -42,7 +49,7 @@ def display(hostname): # create screen and font screen = Image.new("RGB", (display.width, display.height), (0, 0, 0)) screend = ImageDraw.Draw(screen) - screenfont = ImageFont.truetype("/home/adq/DroidSans.ttf", 46) + screenfont = ImageFont.truetype(font_ttf, 46) chartminx = 0 chartw = display.width chartminy = int(display.height / 2) @@ -75,7 +82,7 @@ def display(hostname): continue # we don't need to update ALL the time - if (datetime.datetime.now() - last_update).total_seconds() < UPDATE_MIN_SECS: + if (datetime.datetime.now() - last_update).total_seconds() < minupdatesecs: continue last_update = datetime.datetime.now() @@ -121,7 +128,13 @@ def display(hostname): cur_temp_y = ((cur_temp - mintemp) * charth) / temprange screend.line([chartminx + cur_time_x, chartminy, chartminx + cur_time_x, chartminy - charth], fill='blue') - # show the where we are + # draw target temperature + target = int(msg['target']) + text = f"{target}°" + (tw, th) = screenfont.getsize(text) + screend.text((display.width - tw, display.height - th), text, font=screenfont, fill='blue') + + # show where we are time_done = msg['runtime'] if msg['runtime'] > 0 else 0 time_done_mins = int((time_done / 60) % 60) time_done_hours = int(time_done / 60 / 60) @@ -142,6 +155,8 @@ def display(hostname): if __name__ == "__main__": parser = argparse.ArgumentParser(description='Log kiln data for analysis.') parser.add_argument('--hostname', type=str, default="localhost:8081", help="The kiln-controller hostname:port") + parser.add_argument('--minupdatesecs', type=int, default="10", help="Number of seconds between screen updates") + parser.add_argument('--font_ttf', type=int, default='/home/pi/DroidSans.ttf', help="Font to use for text display") args = parser.parse_args() - display(args.hostname) + display(args.hostname, args.minupdatesecs, args.font_ttf) From 56811b2bc8b3ebe05035fcd7320fecb783e47c81 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Thu, 29 Apr 2021 23:55:19 +0100 Subject: [PATCH 7/8] misc tweaks from testing --- kiln-display.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/kiln-display.py b/kiln-display.py index 6bde3b5e..f6f232ae 100644 --- a/kiln-display.py +++ b/kiln-display.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +from os import supports_dir_fd import websocket import json import time @@ -95,11 +96,14 @@ def display(hostname, minupdatesecs, font_ttf): temp = int(msg['temperature']) text = f"{temp}°" (tw, th) = screenfont.getsize(text) - screend.text((0, display.height - th), text, font=screenfont, fill='yellow') + screend.text((0, display.height - th), text, font=screenfont, fill='blue') # inform if we're actively heating if msg.get('heat'): - screend.ellipse((display.width - 25, display.height - 25, display.width - 5, display.height - 5), fill='red') + spot_radius = 20 + spot_x = (display.width - spot_radius) / 2 + spot_y = display.height - spot_radius - 5 + screend.ellipse((spot_x, spot_y, spot_x + spot_radius, spot_y + spot_radius), fill='red') # if we have a profile, show details of that! if cur_profile: @@ -132,7 +136,7 @@ def display(hostname, minupdatesecs, font_ttf): target = int(msg['target']) text = f"{target}°" (tw, th) = screenfont.getsize(text) - screend.text((display.width - tw, display.height - th), text, font=screenfont, fill='blue') + screend.text((display.width - tw, display.height - th), text, font=screenfont, fill='yellow') # show where we are time_done = msg['runtime'] if msg['runtime'] > 0 else 0 @@ -156,7 +160,7 @@ def display(hostname, minupdatesecs, font_ttf): parser = argparse.ArgumentParser(description='Log kiln data for analysis.') parser.add_argument('--hostname', type=str, default="localhost:8081", help="The kiln-controller hostname:port") parser.add_argument('--minupdatesecs', type=int, default="10", help="Number of seconds between screen updates") - parser.add_argument('--font_ttf', type=int, default='/home/pi/DroidSans.ttf', help="Font to use for text display") + parser.add_argument('--font_ttf', type=str, default='/home/pi/DroidSans.ttf', help="Font to use for text display") args = parser.parse_args() display(args.hostname, args.minupdatesecs, args.font_ttf) From 60c78175a3af54d491292726ca47980c34a0fe30 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Wed, 26 May 2021 00:49:55 +0100 Subject: [PATCH 8/8] add beeper to display --- config.py | 19 +++++++++++-------- kiln-display.py | 46 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/config.py b/config.py index 10d3a247..78483233 100644 --- a/config.py +++ b/config.py @@ -49,6 +49,9 @@ ### value is used. sensor_time_wait = 2 +# GPIO to use for the beeper +gpio_beeper = 12 + ######################################################################## # @@ -57,7 +60,7 @@ # These parameters work well with the simulated oven. You must tune them # to work well with your specific kiln. Note that the integral pid_ki is # inverted so that a smaller number means more integral action. -pid_kp = 25 # Proportional +pid_kp = 25 # Proportional pid_ki = 200 # Integral pid_kd = 200 # Derivative @@ -66,11 +69,11 @@ # # Initial heating and Integral Windup # -# During initial heating, if the temperature is constantly under the +# During initial heating, if the temperature is constantly under the # setpoint,large amounts of Integral can accumulate. This accumulation # causes the kiln to run above the setpoint for potentially a long # period of time. These settings allow integral accumulation only when -# the temperature is within stop_integral_windup_margin percent below +# the temperature is within stop_integral_windup_margin percent below # or above the setpoint. This applies only to the integral. stop_integral_windup = True stop_integral_windup_margin = 10 @@ -96,20 +99,20 @@ # If you change the temp_scale, all settings in this file are assumed to # be in that scale. -temp_scale = "f" # c = Celsius | f = Fahrenheit - Unit to display +temp_scale = "f" # c = Celsius | f = Fahrenheit - Unit to display time_scale_slope = "h" # s = Seconds | m = Minutes | h = Hours - Slope displayed in temp_scale per time_scale_slope time_scale_profile = "m" # s = Seconds | m = Minutes | h = Hours - Enter and view target time in time_scale_profile # emergency shutoff the profile if this temp is reached or exceeded. # This just shuts off the profile. If your SSR is working, your kiln will -# naturally cool off. If your SSR has failed/shorted/closed circuit, this +# naturally cool off. If your SSR has failed/shorted/closed circuit, this # means your kiln receives full power until your house burns down. # this should not replace you watching your kiln or use of a kiln-sitter -emergency_shutoff_temp = 2264 #cone 7 +emergency_shutoff_temp = 2264 #cone 7 -# If the kiln cannot heat or cool fast enough and is off by more than +# If the kiln cannot heat or cool fast enough and is off by more than # kiln_must_catch_up_max_error the entire schedule is shifted until -# the desired temperature is reached. If your kiln cannot attain the +# the desired temperature is reached. If your kiln cannot attain the # wanted temperature, the schedule will run forever. kiln_must_catch_up = True kiln_must_catch_up_max_error = 10 #degrees diff --git a/kiln-display.py b/kiln-display.py index f6f232ae..83686b0b 100644 --- a/kiln-display.py +++ b/kiln-display.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -from os import supports_dir_fd import websocket import json import time @@ -9,7 +8,9 @@ import digitalio import board import adafruit_rgb_display.st7789 as st7789 +import RPi.GPIO as GPIO from PIL import Image, ImageDraw, ImageFont +import config # This is designed to drive an Adafruit Mini PiTFT 1.3" (https://www.adafruit.com/product/4484) # @@ -22,10 +23,33 @@ # non-numpy fallback code will consume much CPU. +def beep(delay): + GPIO.output(config.gpio_beeper, GPIO.HIGH) + time.sleep(delay) + GPIO.output(config.gpio_beeper, GPIO.LOW) + + +def morse(code): + for c in code: + if c == '.': + beep(0.25) + + elif c == '-': + beep(0.5) + + time.sleep(0.25) + + def display(hostname, minupdatesecs, font_ttf): status_ws = websocket.WebSocket() storage_ws = websocket.WebSocket() + # setup beeper GPIO + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(config.gpio_beeper, GPIO.OUT) + GPIO.output(config.gpio_beeper, GPIO.LOW) + # Configuration for CS and DC pins for Raspberry Pi cs_pin = digitalio.DigitalInOut(board.CE0) dc_pin = digitalio.DigitalInOut(board.D25) @@ -57,6 +81,7 @@ def display(hostname, minupdatesecs, font_ttf): charth = int(display.height / 2) # main loop + state = 'idle' cur_profile = None last_update = datetime.datetime.now() while True: @@ -82,6 +107,25 @@ def display(hostname, minupdatesecs, font_ttf): continue + if state == 'idle' and cur_profile: + state = 'profile_tempok' + + elif state != 'idle' and not cur_profile: + state = 'idle' + morse('-.-.') # (C) Profile Complete + + if state == 'profile_tempok': + tempdelta = abs(msg.get('temperature', 0) - msg.get('target', 0)) + if tempdelta > 5: + state = 'profile_catchup' + morse('....') # (H) Temp bad + + elif state == 'profile_catchup': + tempdelta = abs(msg.get('temperature', 0) - msg.get('target', 0)) + if tempdelta < 1: + state = 'profile_tempok' + morse('-') # (T) Temp ok + # we don't need to update ALL the time if (datetime.datetime.now() - last_update).total_seconds() < minupdatesecs: continue