Update
This commit is contained in:
parent
b0988ade10
commit
f73636701c
BIN
lib/.DS_Store
vendored
BIN
lib/.DS_Store
vendored
Binary file not shown.
157
lib/oven.py
157
lib/oven.py
@ -8,6 +8,7 @@ import config
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Output(object):
|
||||
def __init__(self):
|
||||
self.active = False
|
||||
@ -26,13 +27,15 @@ class Output(object):
|
||||
log.warning(msg)
|
||||
self.active = False
|
||||
|
||||
def heat(self,sleepfor):
|
||||
def heat(self,sleepfor, tuning=False):
|
||||
self.GPIO.output(config.gpio_heat, self.GPIO.HIGH)
|
||||
if tuning:
|
||||
return
|
||||
time.sleep(sleepfor)
|
||||
self.GPIO.output(config.gpio_heat, self.GPIO.LOW)
|
||||
|
||||
def cool(self,sleepfor):
|
||||
'''no active cooling, so sleep'''
|
||||
self.GPIO.output(config.gpio_heat, self.GPIO.LOW)
|
||||
time.sleep(sleepfor)
|
||||
|
||||
# FIX - Board class needs to be completely removed
|
||||
@ -54,7 +57,7 @@ class Board(object):
|
||||
self.active = True
|
||||
log.info("import %s " % (self.name))
|
||||
except ImportError:
|
||||
msg = "max31855 config set, but import failed"
|
||||
msg = "max31855 config set, but import failed"
|
||||
log.warning(msg)
|
||||
|
||||
if config.max31856:
|
||||
@ -64,7 +67,7 @@ class Board(object):
|
||||
self.active = True
|
||||
log.info("import %s " % (self.name))
|
||||
except ImportError:
|
||||
msg = "max31856 config set, but import failed"
|
||||
msg = "max31856 config set, but import failed"
|
||||
log.warning(msg)
|
||||
|
||||
def create_temp_sensor(self):
|
||||
@ -76,13 +79,15 @@ class Board(object):
|
||||
class BoardSimulated(object):
|
||||
def __init__(self):
|
||||
self.temp_sensor = TempSensorSimulated()
|
||||
|
||||
|
||||
class TempSensor(threading.Thread):
|
||||
def __init__(self):
|
||||
threading.Thread.__init__(self)
|
||||
self.daemon = True
|
||||
self.temperature = 0
|
||||
self.bad_percent = 0
|
||||
self.time_step = config.sensor_time_wait
|
||||
self.noConnection = self.shortToGround = self.shortToVCC = self.unknownError = False
|
||||
|
||||
class TempSensorSimulated(TempSensor):
|
||||
'''not much here, just need to be able to set the temperature'''
|
||||
@ -94,6 +99,11 @@ class TempSensorReal(TempSensor):
|
||||
during the time_step'''
|
||||
def __init__(self):
|
||||
TempSensor.__init__(self)
|
||||
self.sleeptime = self.time_step / float(config.temperature_average_samples)
|
||||
self.bad_count = 0
|
||||
self.ok_count = 0
|
||||
self.bad_stamp = 0
|
||||
|
||||
if config.max31855:
|
||||
log.info("init MAX31855")
|
||||
from max31855 import MAX31855, MAX31855Error
|
||||
@ -108,31 +118,50 @@ class TempSensorReal(TempSensor):
|
||||
software_spi = { 'cs': config.gpio_sensor_cs,
|
||||
'clk': config.gpio_sensor_clock,
|
||||
'do': config.gpio_sensor_data,
|
||||
### MARK TILLES ADDED
|
||||
'di': config.gpio_sensor_di,
|
||||
#'gpio': config.gpio_heat
|
||||
}
|
||||
|
||||
'di': config.gpio_sensor_di }
|
||||
self.thermocouple = MAX31856(tc_type=config.thermocouple_type,
|
||||
software_spi = software_spi,
|
||||
units = config.temp_scale
|
||||
units = config.temp_scale,
|
||||
ac_freq_50hz = config.ac_freq_50hz,
|
||||
)
|
||||
|
||||
def run(self):
|
||||
'''take 5 measurements over each time period and return the
|
||||
average'''
|
||||
'''use a moving average of config.temperature_average_samples across the time_step'''
|
||||
temps = []
|
||||
while True:
|
||||
maxtries = 5
|
||||
sleeptime = self.time_step / float(maxtries)
|
||||
temps = []
|
||||
for x in range(0,maxtries):
|
||||
try:
|
||||
temp = self.thermocouple.get()
|
||||
temps.append(temp)
|
||||
except Exception:
|
||||
log.exception("problem reading temp")
|
||||
time.sleep(sleeptime)
|
||||
self.temperature = sum(temps)/len(temps)
|
||||
# reset error counter if time is up
|
||||
if (time.time() - self.bad_stamp) > (self.time_step * 2):
|
||||
if self.bad_count + self.ok_count:
|
||||
self.bad_percent = (self.bad_count / (self.bad_count + self.ok_count)) * 100
|
||||
else:
|
||||
self.bad_percent = 0
|
||||
self.bad_count = 0
|
||||
self.ok_count = 0
|
||||
self.bad_stamp = time.time()
|
||||
|
||||
temp = self.thermocouple.get()
|
||||
self.noConnection = self.thermocouple.noConnection
|
||||
self.shortToGround = self.thermocouple.shortToGround
|
||||
self.shortToVCC = self.thermocouple.shortToVCC
|
||||
self.unknownError = self.thermocouple.unknownError
|
||||
|
||||
is_bad_value = self.noConnection | self.unknownError
|
||||
if config.honour_theromocouple_short_errors:
|
||||
is_bad_value |= self.shortToGround | self.shortToVCC
|
||||
|
||||
if not is_bad_value:
|
||||
temps.append(temp)
|
||||
if len(temps) > config.temperature_average_samples:
|
||||
del temps[0]
|
||||
self.ok_count += 1
|
||||
|
||||
else:
|
||||
log.error("Problem reading temp N/C:%s GND:%s VCC:%s ???:%s" % (self.noConnection,self.shortToGround,self.shortToVCC,self.unknownError))
|
||||
self.bad_count += 1
|
||||
|
||||
if len(temps):
|
||||
self.temperature = sum(temps) / len(temps)
|
||||
time.sleep(self.sleeptime)
|
||||
|
||||
class Oven(threading.Thread):
|
||||
'''parent oven class. this has all the common code
|
||||
@ -155,8 +184,22 @@ class Oven(threading.Thread):
|
||||
self.pid = PID(ki=config.pid_ki, kd=config.pid_kd, kp=config.pid_kp)
|
||||
|
||||
def run_profile(self, profile, startat=0):
|
||||
log.info("Running schedule %s" % profile.name)
|
||||
self.reset()
|
||||
|
||||
if self.board.temp_sensor.noConnection:
|
||||
log.info("Refusing to start profile - thermocouple not connected")
|
||||
return
|
||||
if self.board.temp_sensor.shortToGround:
|
||||
log.info("Refusing to start profile - thermocouple short to ground")
|
||||
return
|
||||
if self.board.temp_sensor.shortToVCC:
|
||||
log.info("Refusing to start profile - thermocouple short to VCC")
|
||||
return
|
||||
if self.board.temp_sensor.unknownError:
|
||||
log.info("Refusing to start profile - thermocouple unknown error")
|
||||
return
|
||||
|
||||
log.info("Running schedule %s" % profile.name)
|
||||
self.profile = profile
|
||||
self.totaltime = profile.get_duration()
|
||||
self.state = "RUNNING"
|
||||
@ -186,6 +229,9 @@ class Oven(threading.Thread):
|
||||
|
||||
def update_runtime(self):
|
||||
runtime_delta = datetime.datetime.now() - self.start_time
|
||||
if runtime_delta.total_seconds() < 0:
|
||||
runtime_delta = datetime.timedelta(0)
|
||||
|
||||
if self.startat > 0:
|
||||
self.runtime = self.startat + runtime_delta.total_seconds()
|
||||
else:
|
||||
@ -195,12 +241,24 @@ class Oven(threading.Thread):
|
||||
self.target = self.profile.get_target_temperature(self.runtime)
|
||||
|
||||
def reset_if_emergency(self):
|
||||
'''reset if the temperature is way TOO HOT'''
|
||||
'''reset if the temperature is way TOO HOT, or other critical errors detected'''
|
||||
if (self.board.temp_sensor.temperature + config.thermocouple_offset >=
|
||||
config.emergency_shutoff_temp):
|
||||
log.info("emergency!!! temperature too high, shutting down")
|
||||
self.reset()
|
||||
|
||||
if self.board.temp_sensor.noConnection:
|
||||
log.info("emergency!!! lost connection to thermocouple, shutting down")
|
||||
self.reset()
|
||||
|
||||
if self.board.temp_sensor.unknownError:
|
||||
log.info("emergency!!! unknown thermocouple error, shutting down")
|
||||
self.reset()
|
||||
|
||||
if self.board.temp_sensor.bad_percent > 30:
|
||||
log.info("emergency!!! too many errors in a short period, shutting down")
|
||||
self.reset()
|
||||
|
||||
def reset_if_schedule_ended(self):
|
||||
if self.runtime > self.totaltime:
|
||||
log.info("schedule ended, shutting down")
|
||||
@ -214,6 +272,8 @@ class Oven(threading.Thread):
|
||||
'state': self.state,
|
||||
'heat': self.heat,
|
||||
'totaltime': self.totaltime,
|
||||
'kwh_rate': config.kwh_rate,
|
||||
'currency_type': config.currency_type,
|
||||
'profile': self.profile.name if self.profile else None,
|
||||
}
|
||||
return state
|
||||
@ -249,10 +309,10 @@ class SimulatedOven(Oven):
|
||||
# set temps to the temp of the surrounding environment
|
||||
self.t = self.t_env # deg C temp of oven
|
||||
self.t_h = self.t_env #deg C temp of heating element
|
||||
|
||||
|
||||
# call parent init
|
||||
Oven.__init__(self)
|
||||
|
||||
|
||||
# start thread
|
||||
self.start()
|
||||
log.info("SimulatedOven started")
|
||||
@ -310,9 +370,9 @@ class SimulatedOven(Oven):
|
||||
self.runtime,
|
||||
self.totaltime,
|
||||
time_left))
|
||||
|
||||
|
||||
# we don't actually spend time heating & cooling during
|
||||
# a simulation, so sleep.
|
||||
# a simulation, so sleep.
|
||||
time.sleep(self.time_step)
|
||||
|
||||
|
||||
@ -329,6 +389,10 @@ class RealOven(Oven):
|
||||
# start thread
|
||||
self.start()
|
||||
|
||||
def reset(self):
|
||||
super().reset()
|
||||
self.output.cool(0)
|
||||
|
||||
def heat_then_cool(self):
|
||||
pid = self.pid.compute(self.target,
|
||||
self.board.temp_sensor.temperature +
|
||||
@ -341,8 +405,10 @@ class RealOven(Oven):
|
||||
if heat_on > 0:
|
||||
self.heat = 1.0
|
||||
|
||||
self.output.heat(heat_on)
|
||||
self.output.cool(heat_off)
|
||||
if heat_on:
|
||||
self.output.heat(heat_on)
|
||||
if heat_off:
|
||||
self.output.cool(heat_off)
|
||||
time_left = self.totaltime - self.runtime
|
||||
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,
|
||||
@ -400,7 +466,7 @@ class PID():
|
||||
self.lastErr = 0
|
||||
|
||||
# FIX - this was using a really small window where the PID control
|
||||
# takes effect from -1 to 1. I changed this to various numbers and
|
||||
# takes effect from -1 to 1. I changed this to various numbers and
|
||||
# settled on -50 to 50 and then divide by 50 at the end. This results
|
||||
# in a larger PID control window and much more accurate control...
|
||||
# instead of what used to be binary on/off control.
|
||||
@ -413,8 +479,12 @@ class PID():
|
||||
error = float(setpoint - ispoint)
|
||||
|
||||
if self.ki > 0:
|
||||
self.iterm += (error * timeDelta * (1/self.ki))
|
||||
|
||||
if config.stop_integral_windup == True:
|
||||
if abs(self.kp * error) < window_size:
|
||||
self.iterm += (error * timeDelta * (1/self.ki))
|
||||
else:
|
||||
self.iterm += (error * timeDelta * (1/self.ki))
|
||||
|
||||
dErr = (error - self.lastErr) / timeDelta
|
||||
output = self.kp * error + self.iterm + self.kd * dErr
|
||||
out4logs = output
|
||||
@ -426,15 +496,16 @@ class PID():
|
||||
if output < 0:
|
||||
output = 0
|
||||
|
||||
#if output > 1:
|
||||
# output = 1
|
||||
|
||||
output = float(output / window_size)
|
||||
|
||||
if out4logs > 0:
|
||||
log.info("pid percents pid=%0.2f p=%0.2f i=%0.2f d=%0.2f" % (out4logs,
|
||||
((self.kp * error)/out4logs)*100,
|
||||
(self.iterm/out4logs)*100,
|
||||
((self.kd * dErr)/out4logs)*100))
|
||||
if out4logs > 0:
|
||||
# log.info("pid percents pid=%0.2f p=%0.2f i=%0.2f d=%0.2f" % (out4logs,
|
||||
# ((self.kp * error)/out4logs)*100,
|
||||
# (self.iterm/out4logs)*100,
|
||||
# ((self.kd * dErr)/out4logs)*100))
|
||||
log.info("pid actuals pid=%0.2f p=%0.2f i=%0.2f d=%0.2f" % (out4logs,
|
||||
self.kp * error,
|
||||
self.iterm,
|
||||
self.kd * dErr))
|
||||
|
||||
return output
|
||||
|
||||
BIN
public/.DS_Store
vendored
BIN
public/.DS_Store
vendored
Binary file not shown.
Loading…
Reference in New Issue
Block a user