rebuild temperature error handling and smoothing
This commit is contained in:
parent
712ad761af
commit
9b08342038
@ -13,7 +13,7 @@ class MAX31855(object):
|
||||
'''Initialize Soft (Bitbang) SPI bus
|
||||
|
||||
Parameters:
|
||||
- cs_pin: Chip Select (CS) / Slave Select (SS) pin (Any GPIO)
|
||||
- cs_pin: Chip Select (CS) / Slave Select (SS) pin (Any GPIO)
|
||||
- clock_pin: Clock (SCLK / SCK) pin (Any GPIO)
|
||||
- data_pin: Data input (SO / MOSI) pin (Any GPIO)
|
||||
- units: (optional) unit of measurement to return. ("c" (default) | "k" | "f")
|
||||
@ -26,6 +26,7 @@ class MAX31855(object):
|
||||
self.units = units
|
||||
self.data = None
|
||||
self.board = board
|
||||
self.noConnection = self.shortToGround = self.shortToVCC = False
|
||||
|
||||
# Initialize needed GPIO
|
||||
GPIO.setmode(self.board)
|
||||
@ -70,20 +71,12 @@ class MAX31855(object):
|
||||
if data_32 is None:
|
||||
data_32 = self.data
|
||||
anyErrors = (data_32 & 0x10000) != 0 # Fault bit, D16
|
||||
noConnection = (data_32 & 0x00000001) != 0 # OC bit, D0
|
||||
shortToGround = (data_32 & 0x00000002) != 0 # SCG bit, D1
|
||||
shortToVCC = (data_32 & 0x00000004) != 0 # SCV bit, D2
|
||||
if anyErrors:
|
||||
if noConnection:
|
||||
raise MAX31855Error("No Connection")
|
||||
elif shortToGround:
|
||||
raise MAX31855Error("Thermocouple short to ground")
|
||||
elif shortToVCC:
|
||||
raise MAX31855Error("Thermocouple short to VCC")
|
||||
else:
|
||||
# Perhaps another SPI device is trying to send data?
|
||||
# Did you remember to initialize all other SPI devices?
|
||||
raise MAX31855Error("Unknown Error")
|
||||
self.noConnection = (data_32 & 0x00000001) != 0 # OC bit, D0
|
||||
self.shortToGround = (data_32 & 0x00000002) != 0 # SCG bit, D1
|
||||
self.shortToVCC = (data_32 & 0x00000004) != 0
|
||||
else:
|
||||
self.noConnection = self.shortToGround = self.shortToVCC = False
|
||||
|
||||
def data_to_tc_temperature(self, data_32 = None):
|
||||
'''Takes an integer and returns a thermocouple temperature in celsius.'''
|
||||
@ -138,7 +131,7 @@ class MAX31855(object):
|
||||
GPIO.setup(self.clock_pin, GPIO.IN)
|
||||
|
||||
def data_to_LinearizedTempC(self, data_32 = None):
|
||||
'''Return the NIST-linearized thermocouple temperature value in degrees
|
||||
'''Return the NIST-linearized thermocouple temperature value in degrees
|
||||
celsius. See https://learn.adafruit.com/calibrating-sensors/maxim-31855-linearization for more infoo.
|
||||
This code came from https://github.com/nightmechanic/FuzzypicoReflow/blob/master/lib/max31855.py
|
||||
'''
|
||||
|
||||
72
lib/oven.py
72
lib/oven.py
@ -8,6 +8,8 @@ import config
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
TEMPERATURE_MOVING_AVERAGE_SAMPLES = 40
|
||||
|
||||
class Output(object):
|
||||
def __init__(self):
|
||||
self.active = False
|
||||
@ -54,7 +56,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 +66,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 +78,14 @@ 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.time_step = config.sensor_time_wait
|
||||
self.noConnection = self.shortToGround = self.shortToVCC = False
|
||||
|
||||
class TempSensorSimulated(TempSensor):
|
||||
'''not much here, just need to be able to set the temperature'''
|
||||
@ -94,6 +97,8 @@ class TempSensorReal(TempSensor):
|
||||
during the time_step'''
|
||||
def __init__(self):
|
||||
TempSensor.__init__(self)
|
||||
self.sleeptime = self.time_step / float(TEMPERATURE_MOVING_AVERAGE_SAMPLES)
|
||||
|
||||
if config.max31855:
|
||||
log.info("init MAX31855")
|
||||
from max31855 import MAX31855, MAX31855Error
|
||||
@ -114,20 +119,20 @@ class TempSensorReal(TempSensor):
|
||||
)
|
||||
|
||||
def run(self):
|
||||
'''take 5 measurements over each time period and return the
|
||||
average'''
|
||||
'''use a moving average of TEMPERATURE_AVERAGE_WINDOW 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)
|
||||
temp = self.thermocouple.get()
|
||||
temps.append(temp)
|
||||
if len(temps) > TEMPERATURE_MOVING_AVERAGE_SAMPLES:
|
||||
del temps[0]
|
||||
|
||||
if len(temps):
|
||||
self.temperature = sum(temps) / len(temps)
|
||||
self.noConnection = self.thermocouple.noConnection
|
||||
self.shortToGround = self.thermocouple.shortToGround
|
||||
self.shortToVCC = self.thermocouple.shortToVCC
|
||||
time.sleep(self.sleeptime)
|
||||
|
||||
class Oven(threading.Thread):
|
||||
'''parent oven class. this has all the common code
|
||||
@ -150,8 +155,19 @@ 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
|
||||
|
||||
log.info("Running schedule %s" % profile.name)
|
||||
self.profile = profile
|
||||
self.totaltime = profile.get_duration()
|
||||
self.state = "RUNNING"
|
||||
@ -196,6 +212,10 @@ class Oven(threading.Thread):
|
||||
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()
|
||||
|
||||
def reset_if_schedule_ended(self):
|
||||
if self.runtime > self.totaltime:
|
||||
log.info("schedule ended, shutting down")
|
||||
@ -244,10 +264,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")
|
||||
@ -305,9 +325,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)
|
||||
|
||||
|
||||
@ -395,7 +415,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.
|
||||
@ -409,7 +429,7 @@ class PID():
|
||||
|
||||
if self.ki > 0:
|
||||
self.iterm += (error * timeDelta * (1/self.ki))
|
||||
|
||||
|
||||
dErr = (error - self.lastErr) / timeDelta
|
||||
output = self.kp * error + self.iterm + self.kd * dErr
|
||||
out4logs = output
|
||||
@ -426,10 +446,6 @@ class PID():
|
||||
|
||||
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))
|
||||
log.info("pid temp==%0.2f set=%0.2f err=%0.2f p=%0.2f i=%0.2f d=%0.2f pid=%0.2f out=%0.2f" % (ispoint, setpoint, error, self.kp * error, self.iterm, self.kd * dErr, out4logs, output))
|
||||
|
||||
return output
|
||||
|
||||
Loading…
Reference in New Issue
Block a user