rebuild temperature error handling and smoothing

This commit is contained in:
Andrew de Quincey 2021-04-28 23:10:48 +01:00
parent 712ad761af
commit 9b08342038
2 changed files with 52 additions and 43 deletions

View File

@ -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
'''

View File

@ -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