diff --git a/config.py b/config.py index 60dd6fb..9c6f204 100644 --- a/config.py +++ b/config.py @@ -1,7 +1,7 @@ import logging # uncomment this if using MAX-31856 -#from lib.max31856 import MAX31856 +from lib.max31856 import MAX31856 ######################################################################## # @@ -10,6 +10,7 @@ import logging ### Logging log_level = logging.INFO log_format = '%(asctime)s %(levelname)s %(name)s: %(message)s' +log_file = '/var/log/kiln-controller.log' ### Server listening_ip = "0.0.0.0" @@ -43,11 +44,14 @@ gpio_fan = 22 # pin 15 # energized the safety relay. ## Display outputs -gpio_disp1_clk = 20 # pin 38 -gpio_disp1_dat = 21 # pin 40 -gpio_disp2_clk = 16 # pin 36 -gpio_disp2_dat = 26 # pin 37 +temp_disp = {'type': 'TMC1637', + 'pins': {'clock': 16, + 'data': 26}} # pin 36, pin 37 + +time_disp = {'type': 'TMC1637', + 'pins': {'clock': 20, + 'data': 21}} # pin 38, pin 40 gpio_dotstar_clk = 19 # pin 35 gpio_dotstar_dat = 13 # pin 33 diff --git a/kiln-controller.py b/kiln-controller.py index 4482ba8..a02db8d 100755 --- a/kiln-controller.py +++ b/kiln-controller.py @@ -23,7 +23,9 @@ except: print ("Copy config.py.EXAMPLE to config.py and adapt it for your setup.") exit(1) -logging.basicConfig(level=config.log_level, format=config.log_format) +logging.basicConfig(level=config.log_level, + format=config.log_format, + filename=config.log_file) log = logging.getLogger("kiln-controller") log.info("Starting kiln controller") diff --git a/lib/display.py b/lib/display.py new file mode 100644 index 0000000..c109331 --- /dev/null +++ b/lib/display.py @@ -0,0 +1,35 @@ +import tm1637 +import logging + +log = logging.getLogger(__name__) + + +class TM1637(object): + def __init__(self, + clk_pin, + dat_pin): + + self.clk_pin = clk_pin + self.dat_pin = dat_pin + + try: + self.tm = tm1637.TM1637(clk=clk_pin, + dio=dat_pin) + except ImportError as e: + log.warning('import failure: \n%s' % e) + + def temp(self, + t): + self.tm.number(t) + + def time(self, + h, + m): + self.tm.numbers(h, m, True) + + def text(self, + text): + self.tm.show(text[0:4]) + + def off(self): + self.tm.write([0, 0, 0, 0]) diff --git a/lib/oven.py b/lib/oven.py index e20d36a..23ce8e5 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -1,10 +1,12 @@ import threading import time +from math import floor, ceil import random import datetime import logging import json import config +from display import TM1637 log = logging.getLogger(__name__) @@ -13,6 +15,26 @@ class Output(object): def __init__(self): self.active = False self.load_libs() + log.warning('Active: %s' % self.active) + if self.active: + log.info("Trying to initialize displays") + try: + self.time_disp = Display(config.time_disp['type'], + config.time_disp['pins']) + self.time_disp.time(11,11) + except NameError as e: + log.warning("Couldn't initialize time display") + log.warning("Error: %s" % e) + self.time_disp = False + + try: + self.temp_disp = Display(config.temp_disp['type'], + config.temp_disp['pins']) + self.temp_disp.text('----') + except NameError as e: + log.warning("Couldn't initialize temp display") + log.warning("Error: %s" % e) + self.temp_disp = False def load_libs(self): try: @@ -30,11 +52,19 @@ class Output(object): def safety_off(self): '''Energizes the safety relay''' - self.GPIO.output(config.gpio_e_relay, self.GPIO.HIGH) + if self.active: + log.info("energizing safety relay") + self.GPIO.output(config.gpio_e_relay, self.GPIO.HIGH) + else: + log.info("simulating energizing safety relay") def safety_on(self): '''Deenergizes the safety relay''' - self.GPIO.output(config.gpio_e_relay, self.GPIO.LOW) + if self.active: + log.info("deenergizing safety relay") + self.GPIO.output(config.gpio_e_relay, self.GPIO.LOW) + else: + log.info("simulating deenergizing safety relay") def heat(self,sleepfor): self.GPIO.output(config.gpio_heat, self.GPIO.HIGH) @@ -45,6 +75,29 @@ class Output(object): self.GPIO.output(config.gpio_heat, self.GPIO.LOW) time.sleep(sleepfor) +class Display(object): + def __init__(self, + type, + pins): + + if type == "TMC1637": + self.disp = TM1637(pins['clock'], + pins['data']) + + def temp(self, t): + self.disp.temp(t) + + def time(self, h, m): + self.disp.time(h, m) + + def off(self): + self.disp.off() + + def text(self, text): + self.disp.text(text) + + + # FIX - Board class needs to be completely removed class Board(object): def __init__(self): @@ -178,10 +231,20 @@ class Oven(threading.Thread): self.daemon = True self.temperature = 0 self.time_step = config.sensor_time_wait + self.output = Output() + + if self.output.time_disp: + self.output.time_disp.text(self.state) + if self.output.temp_disp: + self.output.temp_disp.temp(self.temperature) + self.reset() def reset(self): + self.output.safety_on() self.state = "IDLE" + if self.output.time_disp: + self.output.time_disp.text(self.state) self.profile = None self.start_time = 0 self.runtime = 0 @@ -192,6 +255,7 @@ class Oven(threading.Thread): def run_profile(self, profile, startat=0): self.reset() + self.output.safety_off() if self.board.temp_sensor.noConnection: log.info("Refusing to start profile - thermocouple not connected") @@ -272,6 +336,10 @@ class Oven(threading.Thread): self.reset() def get_state(self): + + if self.output.temp_disp: + self.output.temp_disp.temp(int(self.board.temp_sensor.temperature)) + state = { 'runtime': self.runtime, 'temperature': self.board.temp_sensor.temperature + config.thermocouple_offset, @@ -393,14 +461,12 @@ class RealOven(Oven): # call parent init Oven.__init__(self) - self.output.safety_off() # start thread self.start() def reset(self): super().reset() - self.output.safety_on() self.output.cool(0) def heat_then_cool(self): @@ -420,6 +486,14 @@ class RealOven(Oven): if heat_off: self.output.cool(heat_off) time_left = self.totaltime - self.runtime + + time_left_h = int(floor(time_left / 3600)) + time_left_m = int(ceil((time_left % 3600) / 60)) + + if self.output.time_disp: + self.output.time_disp.time(time_left_h, + time_left_m) + log.info("temp=%.2f, target=%.2f, pid=%.3f, heat_on=%.2f, heat_off=%.2f, run_time=%d, total_time=%d, time_left=%d" % (self.board.temp_sensor.temperature + config.thermocouple_offset, self.target, diff --git a/lib/ovenWatcher.py b/lib/ovenWatcher.py index 3e47e4f..47d4d74 100644 --- a/lib/ovenWatcher.py +++ b/lib/ovenWatcher.py @@ -1,7 +1,10 @@ import threading,logging,json,time,datetime from oven import Oven +import config + log = logging.getLogger(__name__) + class OvenWatcher(threading.Thread): def __init__(self,oven): self.last_profile = None @@ -9,11 +12,13 @@ class OvenWatcher(threading.Thread): self.started = None self.recording = False self.observers = [] + threading.Thread.__init__(self) self.daemon = True self.oven = oven self.start() + # FIXME - need to save runs of schedules in near-real-time # FIXME - this will enable re-start in case of power outage # FIXME - re-start also requires safety start (pausing at the beginning diff --git a/requirements.txt b/requirements.txt index cbe34f2..4bdeeb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ RPi.GPIO Adafruit-MAX31855 Adafruit-GPIO websocket-client +raspberrypi-tm1637