exception handling completed for 31855 and 31856

pull/123/head
jbruce 2022-11-02 09:31:48 -09:00
rodzic d6163a4c6e
commit 2fa18589a5
6 zmienionych plików z 177 dodań i 749 usunięć

Wyświetl plik

@ -66,17 +66,20 @@ gpio_heat = board.D23 #output that controls relay
### Thermocouple Adapter selection:
# max31855 - bitbang SPI interface
# max31856 - bitbang SPI interface. must specify thermocouple_type.
#max31855 = 1
#max31856 = 0
# see lib/max31856.py for other thermocouple_type, only applies to max31856
max31855 = 1
max31856 = 0
# uncomment this if using MAX-31856
#thermocouple_type = MAX31856.MAX31856_S_TYPE
#thermocouple_type = ThermocoupleType.S
### Thermocouple Connection (using bitbang interfaces)
#gpio_sensor_cs = 27
#gpio_sensor_clock = 22
#gpio_sensor_data = 17
#gpio_sensor_di = 10 # only used with max31856
# here are the possible max-31856 thermocouple types
# ThermocoupleType.B
# ThermocoupleType.E
# ThermocoupleType.J
# ThermocoupleType.K
# ThermocoupleType.N
# ThermocoupleType.R
# ThermocoupleType.S
# ThermocoupleType.T
########################################################################
#
@ -112,7 +115,7 @@ stop_integral_windup = True
########################################################################
#
# Simulation parameters
simulate = True
simulate = False
sim_t_env = 60.0 # deg C
sim_c_heat = 500.0 # J/K heat capacity of heat element
sim_c_oven = 5000.0 # J/K heat capacity of oven
@ -179,16 +182,25 @@ ac_freq_50hz = False
# - unknown error with thermocouple
# - too many errors in a short period from thermocouple
# but in some cases, you might want to ignore a specific error, log it,
# and continue running your profile.
# and continue running your profile instead of having the process die.
#
# You should only set these to True if you experience a problem
# and WANT to ignore it to complete a firing.
ignore_temp_too_high = False
ignore_lost_connection_tc = False
ignore_unknown_tc_error = False
ignore_too_many_tc_errors = False
# some kilns/thermocouples start erroneously reporting "short"
# errors at higher temperatures due to plasma forming in the kiln.
# Set this to True to ignore these errors and assume the temperature
# reading was correct anyway
ignore_tc_lost_connection = False
ignore_tc_cold_junction_range_error = False
ignore_tc_range_error = False
ignore_tc_cold_junction_temp_high = False
ignore_tc_cold_junction_temp_low = False
ignore_tc_temp_high = False
ignore_tc_temp_low = False
ignore_tc_voltage_error = False
ignore_tc_short_errors = False
ignore_tc_unknown_error = False
# This overrides all possible thermocouple errors and prevents the
# process from exiting.
ignore_tc_too_many_errors = False
########################################################################
# automatic restarts - if you have a power brown-out and the raspberry pi

Wyświetl plik

@ -1,265 +0,0 @@
#!/usr/bin/python
import RPi.GPIO as GPIO
import math
class MAX31855(object):
'''Python driver for [MAX38155 Cold-Junction Compensated Thermocouple-to-Digital Converter](http://www.maximintegrated.com/datasheet/index.mvp/id/7273)
Requires:
- The [GPIO Library](https://code.google.com/p/raspberry-gpio-python/) (Already on most Raspberry Pi OS builds)
- A [Raspberry Pi](http://www.raspberrypi.org/)
'''
def __init__(self, cs_pin, clock_pin, data_pin, units = "c", board = GPIO.BCM):
'''Initialize Soft (Bitbang) SPI bus
Parameters:
- 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")
- board: (optional) pin numbering method as per RPi.GPIO library (GPIO.BCM (default) | GPIO.BOARD)
'''
self.cs_pin = cs_pin
self.clock_pin = clock_pin
self.data_pin = data_pin
self.units = units
self.data = None
self.board = board
self.noConnection = self.shortToGround = self.shortToVCC = self.unknownError = False
# Initialize needed GPIO
GPIO.setmode(self.board)
GPIO.setup(self.cs_pin, GPIO.OUT)
GPIO.setup(self.clock_pin, GPIO.OUT)
GPIO.setup(self.data_pin, GPIO.IN)
# Pull chip select high to make chip inactive
GPIO.output(self.cs_pin, GPIO.HIGH)
def get(self):
'''Reads SPI bus and returns current value of thermocouple.'''
self.read()
self.checkErrors()
#return getattr(self, "to_" + self.units)(self.data_to_tc_temperature())
return getattr(self, "to_" + self.units)(self.data_to_LinearizedTempC())
def get_rj(self):
'''Reads SPI bus and returns current value of reference junction.'''
self.read()
return getattr(self, "to_" + self.units)(self.data_to_rj_temperature())
def read(self):
'''Reads 32 bits of the SPI bus & stores as an integer in self.data.'''
bytesin = 0
# Select the chip
GPIO.output(self.cs_pin, GPIO.LOW)
# Read in 32 bits
for i in range(32):
GPIO.output(self.clock_pin, GPIO.LOW)
bytesin = bytesin << 1
if (GPIO.input(self.data_pin)):
bytesin = bytesin | 1
GPIO.output(self.clock_pin, GPIO.HIGH)
# Unselect the chip
GPIO.output(self.cs_pin, GPIO.HIGH)
# Save data
self.data = bytesin
def checkErrors(self, data_32 = None):
'''Checks error bits to see if there are any SCV, SCG, or OC faults'''
if data_32 is None:
data_32 = self.data
anyErrors = (data_32 & 0x10000) != 0 # Fault bit, D16
if anyErrors:
self.noConnection = (data_32 & 0x00000001) != 0 # OC bit, D0
self.shortToGround = (data_32 & 0x00000002) != 0 # SCG bit, D1
self.shortToVCC = (data_32 & 0x00000004) != 0 # SCV bit, D2
self.unknownError = not (self.noConnection | self.shortToGround | self.shortToVCC) # Errk!
else:
self.noConnection = self.shortToGround = self.shortToVCC = self.unknownError = False
def data_to_tc_temperature(self, data_32 = None):
'''Takes an integer and returns a thermocouple temperature in celsius.'''
if data_32 is None:
data_32 = self.data
tc_data = ((data_32 >> 18) & 0x3FFF)
return self.convert_tc_data(tc_data)
def data_to_rj_temperature(self, data_32 = None):
'''Takes an integer and returns a reference junction temperature in celsius.'''
if data_32 is None:
data_32 = self.data
rj_data = ((data_32 >> 4) & 0xFFF)
return self.convert_rj_data(rj_data)
def convert_tc_data(self, tc_data):
'''Convert thermocouple data to a useful number (celsius).'''
if tc_data & 0x2000:
# two's compliment
without_resolution = ~tc_data & 0x1FFF
without_resolution += 1
without_resolution *= -1
else:
without_resolution = tc_data & 0x1FFF
return without_resolution * 0.25
def convert_rj_data(self, rj_data):
'''Convert reference junction data to a useful number (celsius).'''
if rj_data & 0x800:
without_resolution = ~rj_data & 0x7FF
without_resolution += 1
without_resolution *= -1
else:
without_resolution = rj_data & 0x7FF
return without_resolution * 0.0625
def to_c(self, celsius):
'''Celsius passthrough for generic to_* method.'''
return celsius
def to_k(self, celsius):
'''Convert celsius to kelvin.'''
return celsius + 273.15
def to_f(self, celsius):
'''Convert celsius to fahrenheit.'''
return celsius * 9.0/5.0 + 32
def cleanup(self):
'''Selective GPIO cleanup'''
GPIO.setup(self.cs_pin, GPIO.IN)
GPIO.setup(self.clock_pin, GPIO.IN)
def data_to_LinearizedTempC(self, data_32 = None):
'''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
'''
if data_32 is None:
data_32 = self.data
# extract TC temp
# Check if signed bit is set.
if data_32 & 0x80000000:
# Negative value, take 2's compliment. Compute this with subtraction
# because python is a little odd about handling signed/unsigned.
data_32 >>= 18
data_32 -= 16384
else:
# Positive value, just shift the bits to get the value.
data_32 >>= 18
# Scale by 0.25 degrees C per bit and return value.
TC_temp = data_32 * 0.25
# Extract Internal Temp
data_32 = self.data
# Ignore bottom 4 bits of thermocouple data.
data_32 >>= 4
# Grab bottom 11 bits as internal temperature data.
Internal_Temp= data_32 & 0x7FF
if data_32 & 0x800:
# Negative value, take 2's compliment. Compute this with subtraction
# because python is a little odd about handling signed/unsigned.
Internal_Temp -= 4096
# Scale by 0.0625 degrees C per bit and return value.
Internal_Temp = Internal_Temp * 0.0625
# MAX31855 thermocouple voltage reading in mV
thermocoupleVoltage = (TC_temp - Internal_Temp) * 0.041276
# MAX31855 cold junction voltage reading in mV
coldJunctionTemperature = Internal_Temp
coldJunctionVoltage = (-0.176004136860E-01 +
0.389212049750E-01 * coldJunctionTemperature +
0.185587700320E-04 * math.pow(coldJunctionTemperature, 2.0) +
-0.994575928740E-07 * math.pow(coldJunctionTemperature, 3.0) +
0.318409457190E-09 * math.pow(coldJunctionTemperature, 4.0) +
-0.560728448890E-12 * math.pow(coldJunctionTemperature, 5.0) +
0.560750590590E-15 * math.pow(coldJunctionTemperature, 6.0) +
-0.320207200030E-18 * math.pow(coldJunctionTemperature, 7.0) +
0.971511471520E-22 * math.pow(coldJunctionTemperature, 8.0) +
-0.121047212750E-25 * math.pow(coldJunctionTemperature, 9.0) +
0.118597600000E+00 * math.exp(-0.118343200000E-03 * math.pow((coldJunctionTemperature-0.126968600000E+03), 2.0)))
# cold junction voltage + thermocouple voltage
voltageSum = thermocoupleVoltage + coldJunctionVoltage
# calculate corrected temperature reading based on coefficients for 3 different ranges
# float b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10;
if voltageSum < 0:
b0 = 0.0000000E+00
b1 = 2.5173462E+01
b2 = -1.1662878E+00
b3 = -1.0833638E+00
b4 = -8.9773540E-01
b5 = -3.7342377E-01
b6 = -8.6632643E-02
b7 = -1.0450598E-02
b8 = -5.1920577E-04
b9 = 0.0000000E+00
elif voltageSum < 20.644:
b0 = 0.000000E+00
b1 = 2.508355E+01
b2 = 7.860106E-02
b3 = -2.503131E-01
b4 = 8.315270E-02
b5 = -1.228034E-02
b6 = 9.804036E-04
b7 = -4.413030E-05
b8 = 1.057734E-06
b9 = -1.052755E-08
elif voltageSum < 54.886:
b0 = -1.318058E+02
b1 = 4.830222E+01
b2 = -1.646031E+00
b3 = 5.464731E-02
b4 = -9.650715E-04
b5 = 8.802193E-06
b6 = -3.110810E-08
b7 = 0.000000E+00
b8 = 0.000000E+00
b9 = 0.000000E+00
else:
# TODO: handle error - out of range
return 0
return (b0 +
b1 * voltageSum +
b2 * pow(voltageSum, 2.0) +
b3 * pow(voltageSum, 3.0) +
b4 * pow(voltageSum, 4.0) +
b5 * pow(voltageSum, 5.0) +
b6 * pow(voltageSum, 6.0) +
b7 * pow(voltageSum, 7.0) +
b8 * pow(voltageSum, 8.0) +
b9 * pow(voltageSum, 9.0))
class MAX31855Error(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
if __name__ == "__main__":
# Multi-chip example
import time
cs_pins = [4, 17, 18, 24]
clock_pin = 23
data_pin = 22
units = "f"
thermocouples = []
for cs_pin in cs_pins:
thermocouples.append(MAX31855(cs_pin, clock_pin, data_pin, units))
running = True
while(running):
try:
for thermocouple in thermocouples:
rj = thermocouple.get_rj()
try:
tc = thermocouple.get()
except MAX31855Error as e:
tc = "Error: "+ e.value
running = False
print("tc: {} and rj: {}".format(tc, rj))
time.sleep(1)
except KeyboardInterrupt:
running = False
for thermocouple in thermocouples:
thermocouple.cleanup()

Wyświetl plik

@ -1,36 +0,0 @@
#!/usr/bin/python
import logging
from Adafruit_MAX31855 import MAX31855
class MAX31855SPI(object):
'''Python driver for [MAX38155 Cold-Junction Compensated Thermocouple-to-Digital Converter](http://www.maximintegrated.com/datasheet/index.mvp/id/7273)
Requires:
- adafruit's MAX31855 SPI-only device library
'''
def __init__(self, spi_dev):
self.max31855 = MAX31855.MAX31855(spi=spi_dev)
self.log = logging.getLogger(__name__)
def get(self):
'''Reads SPI bus and returns current value of thermocouple.'''
state = self.max31855.readState()
self.log.debug("status %s" % state)
if state['openCircuit']:
raise MAX31855Error('Not Connected')
elif state['shortGND']:
raise MAX31855Error('Short to Ground')
elif state['shortVCC']:
raise MAX31855Error('Short to VCC')
elif state['fault']:
raise MAX31855Error('Unknown Error')
return self.max31855.readLinearizedTempC()
class MAX31855SPIError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)

Wyświetl plik

@ -1,341 +0,0 @@
"""
max31856.py
Class which defines interaction with the MAX31856 sensor.
Copyright (c) 2019 John Robinson
Author: John Robinson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import logging
import warnings
import Adafruit_GPIO as Adafruit_GPIO
import Adafruit_GPIO.SPI as SPI
class MAX31856(object):
"""Class to represent an Adafruit MAX31856 thermocouple temperature
measurement board.
"""
# Board Specific Constants
MAX31856_CONST_THERM_LSB = 2**-7
MAX31856_CONST_THERM_BITS = 19
MAX31856_CONST_CJ_LSB = 2**-6
MAX31856_CONST_CJ_BITS = 14
### Register constants, see data sheet Table 6 (in Rev. 0) for info.
# Read Addresses
MAX31856_REG_READ_CR0 = 0x00
MAX31856_REG_READ_CR1 = 0x01
MAX31856_REG_READ_MASK = 0x02
MAX31856_REG_READ_CJHF = 0x03
MAX31856_REG_READ_CJLF = 0x04
MAX31856_REG_READ_LTHFTH = 0x05
MAX31856_REG_READ_LTHFTL = 0x06
MAX31856_REG_READ_LTLFTH = 0x07
MAX31856_REG_READ_LTLFTL = 0x08
MAX31856_REG_READ_CJTO = 0x09
MAX31856_REG_READ_CJTH = 0x0A # Cold-Junction Temperature Register, MSB
MAX31856_REG_READ_CJTL = 0x0B # Cold-Junction Temperature Register, LSB
MAX31856_REG_READ_LTCBH = 0x0C # Linearized TC Temperature, Byte 2
MAX31856_REG_READ_LTCBM = 0x0D # Linearized TC Temperature, Byte 1
MAX31856_REG_READ_LTCBL = 0x0E # Linearized TC Temperature, Byte 0
MAX31856_REG_READ_FAULT = 0x0F # Fault status register
# Write Addresses
MAX31856_REG_WRITE_CR0 = 0x80
MAX31856_REG_WRITE_CR1 = 0x81
MAX31856_REG_WRITE_MASK = 0x82
MAX31856_REG_WRITE_CJHF = 0x83
MAX31856_REG_WRITE_CJLF = 0x84
MAX31856_REG_WRITE_LTHFTH = 0x85
MAX31856_REG_WRITE_LTHFTL = 0x86
MAX31856_REG_WRITE_LTLFTH = 0x87
MAX31856_REG_WRITE_LTLFTL = 0x88
MAX31856_REG_WRITE_CJTO = 0x89
MAX31856_REG_WRITE_CJTH = 0x8A # Cold-Junction Temperature Register, MSB
MAX31856_REG_WRITE_CJTL = 0x8B # Cold-Junction Temperature Register, LSB
# Pre-config Register Options
MAX31856_CR0_READ_ONE = 0x40 # One shot reading, delay approx. 200ms then read temp registers
MAX31856_CR0_READ_CONT = 0x80 # Continuous reading, delay approx. 100ms between readings
# Thermocouple Types
MAX31856_B_TYPE = 0x0 # Read B Type Thermocouple
MAX31856_E_TYPE = 0x1 # Read E Type Thermocouple
MAX31856_J_TYPE = 0x2 # Read J Type Thermocouple
MAX31856_K_TYPE = 0x3 # Read K Type Thermocouple
MAX31856_N_TYPE = 0x4 # Read N Type Thermocouple
MAX31856_R_TYPE = 0x5 # Read R Type Thermocouple
MAX31856_S_TYPE = 0x6 # Read S Type Thermocouple
MAX31856_T_TYPE = 0x7 # Read T Type Thermocouple
def __init__(self, tc_type=MAX31856_S_TYPE, units="c", avgsel=0x0, ac_freq_50hz=False, ocdetect=0x1, software_spi=None, hardware_spi=None, gpio=None):
"""
Initialize MAX31856 device with software SPI on the specified CLK,
CS, and DO pins. Alternatively can specify hardware SPI by sending an
SPI.SpiDev device in the spi parameter.
Args:
tc_type (1-byte Hex): Type of Thermocouple. Choose from class variables of the form
MAX31856.MAX31856_X_TYPE.
avgsel (1-byte Hex): Type of Averaging. Choose from values in CR0 table of datasheet.
Default is single sample.
ac_freq_50hz: Set to True if your AC frequency is 50Hz, Set to False for 60Hz,
ocdetect: Detect open circuit errors (ie broken thermocouple). Choose from values in CR1 table of datasheet
software_spi (dict): Contains the pin assignments for software SPI, as defined below:
clk (integer): Pin number for software SPI clk
cs (integer): Pin number for software SPI cs
do (integer): Pin number for software SPI MISO
di (integer): Pin number for software SPI MOSI
hardware_spi (SPI.SpiDev): If using hardware SPI, define the connection
"""
self._logger = logging.getLogger('Adafruit_MAX31856.MAX31856')
self._spi = None
self.tc_type = tc_type
self.avgsel = avgsel
self.units = units
self.noConnection = self.shortToGround = self.shortToVCC = self.unknownError = False
# Handle hardware SPI
if hardware_spi is not None:
self._logger.debug('Using hardware SPI')
self._spi = hardware_spi
elif software_spi is not None:
self._logger.debug('Using software SPI')
# Default to platform GPIO if not provided.
if gpio is None:
gpio = Adafruit_GPIO.get_platform_gpio()
self._spi = SPI.BitBang(gpio, software_spi['clk'], software_spi['di'],
software_spi['do'], software_spi['cs'])
else:
raise ValueError(
'Must specify either spi for for hardware SPI or clk, cs, and do for softwrare SPI!')
self._spi.set_clock_hz(5000000)
# According to Wikipedia (on SPI) and MAX31856 Datasheet:
# SPI mode 1 corresponds with correct timing, CPOL = 0, CPHA = 1
self._spi.set_mode(1)
self._spi.set_bit_order(SPI.MSBFIRST)
self.cr0 = self.MAX31856_CR0_READ_CONT | ((ocdetect & 3) << 4) | (1 if ac_freq_50hz else 0)
self.cr1 = (((self.avgsel & 7) << 4) + (self.tc_type & 0x0f))
# Setup for reading continuously with T-Type thermocouple
self._write_register(self.MAX31856_REG_WRITE_CR0, 0)
self._write_register(self.MAX31856_REG_WRITE_CR1, self.cr1)
self._write_register(self.MAX31856_REG_WRITE_CR0, self.cr0)
@staticmethod
def _cj_temp_from_bytes(msb, lsb):
"""
Takes in the msb and lsb from a Cold Junction (CJ) temperature reading and converts it
into a decimal value.
This function was removed from readInternalTempC() and moved to its own method to allow for
easier testing with standard values.
Args:
msb (hex): Most significant byte of CJ temperature
lsb (hex): Least significant byte of a CJ temperature
"""
# (((msb w/o +/-) shifted by number of 1 byte above lsb)
# + val_low_byte)
# >> shifted back by # of dead bits
temp_bytes = (((msb & 0x7F) << 8) + lsb) >> 2
if msb & 0x80:
# Negative Value. Scale back by number of bits
temp_bytes -= 2**(MAX31856.MAX31856_CONST_CJ_BITS -1)
# temp_bytes*value of lsb
temp_c = temp_bytes*MAX31856.MAX31856_CONST_CJ_LSB
return temp_c
@staticmethod
def _thermocouple_temp_from_bytes(byte0, byte1, byte2):
"""
Converts the thermocouple byte values to a decimal value.
This function was removed from readInternalTempC() and moved to its own method to allow for
easier testing with standard values.
Args:
byte2 (hex): Most significant byte of thermocouple temperature
byte1 (hex): Middle byte of thermocouple temperature
byte0 (hex): Least significant byte of a thermocouple temperature
Returns:
temp_c (float): Temperature in degrees celsius
"""
# (((val_high_byte w/o +/-) shifted by 2 bytes above LSB)
# + (val_mid_byte shifted by number 1 byte above LSB)
# + val_low_byte )
# >> back shift by number of dead bits
temp_bytes = (((byte2 & 0x7F) << 16) + (byte1 << 8) + byte0)
temp_bytes = temp_bytes >> 5
if byte2 & 0x80:
temp_bytes -= 2**(MAX31856.MAX31856_CONST_THERM_BITS -1)
# temp_bytes*value of LSB
temp_c = temp_bytes*MAX31856.MAX31856_CONST_THERM_LSB
return temp_c
def read_internal_temp_c(self):
"""
Return internal temperature value in degrees celsius.
"""
val_low_byte = self._read_register(self.MAX31856_REG_READ_CJTL)
val_high_byte = self._read_register(self.MAX31856_REG_READ_CJTH)
temp_c = MAX31856._cj_temp_from_bytes(val_high_byte, val_low_byte)
self._logger.debug("Cold Junction Temperature {0} deg. C".format(temp_c))
return temp_c
def read_temp_c(self):
"""
Return the thermocouple temperature value in degrees celsius.
"""
val_low_byte = self._read_register(self.MAX31856_REG_READ_LTCBL)
val_mid_byte = self._read_register(self.MAX31856_REG_READ_LTCBM)
val_high_byte = self._read_register(self.MAX31856_REG_READ_LTCBH)
temp_c = MAX31856._thermocouple_temp_from_bytes(val_low_byte, val_mid_byte, val_high_byte)
self._logger.debug("Thermocouple Temperature {0} deg. C".format(temp_c))
return temp_c
def read_fault_register(self):
"""Return bytes containing fault codes and hardware problems.
TODO: Could update in the future to return human readable values
"""
reg = self._read_register(self.MAX31856_REG_READ_FAULT)
return reg
def _read_register(self, address):
"""
Reads a register at address from the MAX31856
Args:
address (8-bit Hex): Address for read register. Format 0Xh. Constants listed in class
as MAX31856_REG_READ_*
Note:
SPI transfer method is used. The address is written in as the first byte, and then a
dummy value as the second byte. The data from the sensor is contained in the second
byte, the dummy byte is only used to keep the SPI clock ticking as we read in the
value. The first returned byte is discarded because no data is transmitted while
specifying the register address.
"""
raw = self._spi.transfer([address, 0x00])
if raw is None or len(raw) != 2:
raise RuntimeError('Did not read expected number of bytes from device!')
value = raw[1]
self._logger.debug('Read Register: 0x{0:02X}, Raw Value: 0x{1:02X}'.format(
(address & 0xFFFF), (value & 0xFFFF)))
return value
def _write_register(self, address, write_value):
"""
Writes to a register at address from the MAX31856
Args:
address (8-bit Hex): Address for read register. Format 0Xh. Constants listed in class
as MAX31856_REG_WRITE_*
write_value (8-bit Hex): Value to write to the register
"""
self._spi.transfer([address, write_value])
self._logger.debug('Wrote Register: 0x{0:02X}, Value 0x{1:02X}'.format((address & 0xFF),
(write_value & 0xFF)))
# If we've gotten this far without an exception, the transmission must've gone through
return True
# Deprecated Methods
def readTempC(self): #pylint: disable-msg=invalid-name
"""Depreciated due to Python naming convention, use read_temp_c instead
"""
warnings.warn("Depreciated due to Python naming convention, use read_temp_c() instead", DeprecationWarning)
return read_temp_c(self)
def readInternalTempC(self): #pylint: disable-msg=invalid-name
"""Depreciated due to Python naming convention, use read_internal_temp_c instead
"""
warnings.warn("Depreciated due to Python naming convention, use read_internal_temp_c() instead", DeprecationWarning)
return read_internal_temp_c(self)
# added by jbruce to mimic MAX31855 lib
def to_c(self, celsius):
'''Celsius passthrough for generic to_* method.'''
return celsius
def to_k(self, celsius):
'''Convert celsius to kelvin.'''
return celsius + 273.15
def to_f(self, celsius):
'''Convert celsius to fahrenheit.'''
return celsius * 9.0/5.0 + 32
def checkErrors(self):
data = self.read_fault_register()
self.noConnection = (data & 0x00000001) != 0
self.unknownError = (data & 0xfe) != 0
def get(self):
self.checkErrors()
celcius = self.read_temp_c()
return getattr(self, "to_" + self.units)(celcius)
if __name__ == "__main__":
# Multi-chip example
import time
cs_pins = [6]
clock_pin = 13
data_pin = 5
di_pin = 26
units = "c"
thermocouples = []
for cs_pin in cs_pins:
thermocouples.append(MAX31856(avgsel=0, ac_freq_50hz=True, tc_type=MAX31856.MAX31856_K_TYPE, software_spi={'clk': clock_pin, 'cs': cs_pin, 'do': data_pin, 'di': di_pin}, units=units))
running = True
while(running):
try:
for thermocouple in thermocouples:
rj = thermocouple.read_internal_temp_c()
tc = thermocouple.get()
print("tc: {} and rj: {}, NC:{} ??:{}".format(tc, rj, thermocouple.noConnection, thermocouple.unknownError))
time.sleep(1)
except KeyboardInterrupt:
running = False
for thermocouple in thermocouples:
thermocouple.cleanup()

Wyświetl plik

@ -1,6 +1,5 @@
import threading
import time
import random
import datetime
import logging
import json
@ -30,8 +29,12 @@ class Duplogger():
duplog = Duplogger().logref()
class Output(object):
'''This represents a GPIO output that controls a solid
state relay to turn the kiln elements on and off.
inputs
config.gpio_heat
'''
def __init__(self):
self.active = False
self.heater = digitalio.DigitalInOut(config.gpio_heat)
@ -48,11 +51,18 @@ class Output(object):
# wrapper for blinka board
class Board(object):
'''This represents a blinka board where this code
runs.
'''
def __init__(self):
log.info("board: %s" % (self.name))
self.temp_sensor.start()
class RealBoard(Board):
'''Each board has a thermocouple board attached to it.
Any blinka board that supports SPI can be used. The
board is automatically detected by blinka.
'''
def __init__(self):
self.name = None
self.load_libs()
@ -70,12 +80,18 @@ class RealBoard(Board):
return Max31856()
class SimulatedBoard(Board):
'''Simulated board used during simulations.
See config.simulate
'''
def __init__(self):
self.name = "simulated"
self.temp_sensor = TempSensorSimulated()
Board.__init__(self)
class TempSensor(threading.Thread):
'''Used by the Board class. Each Board must have
a TempSensor.
'''
def __init__(self):
threading.Thread.__init__(self)
self.daemon = True
@ -83,7 +99,7 @@ class TempSensor(threading.Thread):
self.status = ThermocoupleTracker()
class TempSensorSimulated(TempSensor):
'''not much here, just need to be able to set the temperature'''
'''Simulates a temperature sensor '''
def __init__(self):
TempSensor.__init__(self)
self.simulated_temperature = 0
@ -91,8 +107,11 @@ class TempSensorSimulated(TempSensor):
return self.simulated_temperature
class TempSensorReal(TempSensor):
'''real temperature sensor thread that takes N measurements
during the time_step'''
'''real temperature sensor that takes many measurements
during the time_step
inputs
config.temperature_average_samples
'''
def __init__(self):
TempSensor.__init__(self)
self.sleeptime = self.time_step / float(config.temperature_average_samples)
@ -102,35 +121,20 @@ class TempSensorReal(TempSensor):
def get_temperature(self):
'''read temp from tc and convert if needed'''
temp = self.raw_temp() # raw_temp provided by subclasses
if config.temp_scale.lower() == "f":
temp = (temp*9/5)+32
return temp
#def get_temperature(self):
# try:
# temp = self.raw_temp
# if config.temp_scale.lower() == "f":
# temp = (temp*9/5)+32
# log.info("temp = %0.2f" % temp)
# return temp
# except RuntimeError as rte:
# if rte.args and rte.args[0] == "thermocouple not connected":
# self.bad_count = self.bad_count + 1
# if rte.args and rte.args[0] == "short circuit to ground":
# if not config.ignore_tc_short_errors:
# self.bad_count = self.bad_count + 1
# if rte.args and rte.args[0] == "short circuit to power":
# if not config.ignore_tc_short_errors:
# self.bad_count = self.bad_count + 1
# if rte.args and rte.args[0] == "faulty reading":
# self.bad_count = self.bad_count + 1
#
# log.error("Problem reading temp %s" % (rte.args[0]))
# # fix still need to include max-31856 errors by calling fault
# # and checking a dict of possible faults. what a shitty way to handle
# # errors.
# return None
try:
temp = self.raw_temp() # raw_temp provided by subclasses
if config.temp_scale.lower() == "f":
temp = (temp*9/5)+32
self.status.good()
return temp
except ThermocoupleError as tce:
if tce.ignore:
log.error("Problem reading temp (ignored) %s" % (tce.message))
self.status.good()
else:
log.error("Problem reading temp %s" % (tce.message))
self.status.bad()
return None
def temperature(self):
'''average temp over a duty cycle'''
@ -138,20 +142,14 @@ class TempSensorReal(TempSensor):
def run(self):
while True:
# not at all sure this try/except should be here
# might be better getting the temp
try:
temp = self.get_temperature()
self.status.good()
temp = self.get_temperature()
if temp:
self.temptracker.add(temp)
except RuntimeError as rte:
self.status.bad()
log.error("Problem reading temp %s" % (rte.args[0]))
time.sleep(self.sleeptime)
class TempTracker(object):
'''creates a sliding window of temperatures
'''creates a sliding window of N temperatures per
config.sensor_time_wait
'''
def __init__(self):
self.size = config.temperature_average_samples
@ -204,8 +202,6 @@ class ThermocoupleTracker(object):
class Max31855(TempSensorReal):
'''each subclass expected to handle errors and get temperature'''
# FIX I need unified errors from these classes since the underlying
# implementations are different
def __init__(self):
TempSensorReal.__init__(self)
log.info("thermocouple MAX31855")
@ -213,7 +209,83 @@ class Max31855(TempSensorReal):
self.thermocouple = adafruit_max31855.MAX31855(self.spi, self.cs)
def raw_temp(self):
return self.thermocouple.temperature_NIST
try:
return self.thermocouple.temperature_NIST
except RuntimeError as rte:
if rte.args and rte.args[0]:
raise Max31855_Error(rte.args[0])
raise Max31855_Error('unknown')
class ThermocoupleError(Exception):
'''
thermocouple exception parent class to handle mapping of error messages
and make them consistent across adafruit libraries. Also set whether
each exception should be ignored based on settings in config.py.
'''
def __init__(self, message):
self.ignore = False
self.message = message
self.map_message()
self.set_ignore()
super().__init__(self.message)
def set_ignore(self):
if self.message == "not connected" and config.ignore_tc_lost_connection == True:
self.ignore = True
if self.message == "short circuit" and config.ignore_tc_short_errors == True:
self.ignore = True
if self.message == "unknown" and config.ignore_tc_unknown_error == True:
self.ignore = True
if self.message == "cold junction range fault" and config.ignore_tc_cold_junction_range_error == True:
self.ignore = True
if self.message == "thermocouple range fault" and config.ignore_tc_range_error == True:
self.ignore = True
if self.message == "cold junction temp too high" and config.ignore_tc_cold_junction_temp_high == True:
self.ignore = True
if self.message == "cold junction temp too low" and config.ignore_tc_cold_junction_temp_low == True:
self.ignore = True
if self.message == "thermocouple temp too high" and config.ignore_tc_temp_high == True:
self.ignore = True
if self.message == "thermocouple temp too low" and config.ignore_tc_temp_low == True:
self.ignore = True
if self.message == "voltage too high or low" and config.ignore_tc_voltage_error == True:
self.ignore = True
def map_message(self):
try:
self.message = self.map[self.orig_message]
except KeyError:
self.message = "unknown"
class Max31855_Error(ThermocoupleError):
'''
All children must set self.orig_message and self.map
'''
def __init__(self, message):
self.orig_message = message
# this purposefully makes "fault reading" and
# "Total thermoelectric voltage out of range..." unknown errors
self.map = {
"thermocouple not connected" : "not connected",
"short circuit to ground" : "short circuit",
"short circuit to power" : "short circuit",
}
super().__init__(self.message)
class Max31856_Error(ThermocoupleError):
def __init__(self, message):
self.orig_message = message
self.map = {
"cj_range" : "cold junction range fault",
"tc_range" : "thermocouple range fault",
"cj_high" : "cold junction temp too high",
"cj_low" : "cold junction temp too low",
"tc_high" : "thermocouple temp too high",
"tc_low" : "thermocouple temp too low",
"voltage" : "voltage too high or low",
"open_tc" : "not connected"
}
super().__init__(self.message)
class Max31856(TempSensorReal):
'''each subclass expected to handle errors and get temperature'''
@ -229,7 +301,16 @@ class Max31856(TempSensorReal):
self.thermocouple.noise_rejection(60)
def raw_temp(self):
return self.thermocouple.temperature
# The underlying adafruit library does not throw exceptions
# for thermocouple errors. Instead, they are stored in
# dict named self.thermocouple.fault. Here we check that
# dict for errors and raise an exception.
# and raise Max31856_Error(message)
temp = self.thermocouple.temperature
for k,v in self.thermocouple.fault:
if v:
raise Max31856_Error(k)
return temp
class Oven(threading.Thread):
'''parent oven class. this has all the common code
@ -254,21 +335,6 @@ class Oven(threading.Thread):
def run_profile(self, profile, startat=0):
self.reset()
# FIX, these need to be moved
#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
self.startat = startat * 60
self.runtime = self.startat
self.start_time = datetime.datetime.now() - datetime.timedelta(seconds=self.startat)
@ -310,27 +376,16 @@ class Oven(threading.Thread):
def reset_if_emergency(self):
'''reset if the temperature is way TOO HOT, or other critical errors detected'''
# FIX - need to fix this whole thing...
#if (self.board.temp_sensor.temperature() + config.thermocouple_offset >=
# config.emergency_shutoff_temp):
# log.info("emergency!!! temperature too high")
# if config.ignore_temp_too_high == False:
# self.abort_run()
#if self.board.temp_sensor.noConnection:
# log.info("emergency!!! lost connection to thermocouple")
# if config.ignore_lost_connection_tc == False:
# self.abort_run()
#if self.board.temp_sensor.unknownError:
# log.info("emergency!!! unknown thermocouple error")
# if config.ignore_unknown_tc_error == False:
# self.abort_run()
#if self.board.temp_sensor.status.over_error_limit():
# log.info("emergency!!! too many errors in a short period")
# if config.ignore_too_many_tc_errors == False:
# self.abort_run()
if (self.board.temp_sensor.temperature() + config.thermocouple_offset >=
config.emergency_shutoff_temp):
log.info("emergency!!! temperature too high")
if config.ignore_temp_too_high == False:
self.abort_run()
if self.board.temp_sensor.status.over_error_limit():
log.info("emergency!!! too many errors in a short period")
if config.ignore_too_many_tc_errors == False:
self.abort_run()
def reset_if_schedule_ended(self):
if self.runtime > self.totaltime:

Wyświetl plik

@ -6,9 +6,12 @@ gevent-websocket
websocket-client
#RPi.GPIO
#Adafruit-MAX31855
#Adafruit-GPIO
adafruit-circuitpython-max31856
# List of all supported adafruit modules for thermocouples
adafruit-circuitpython-max31855
adafruit-circuitpython-max31856
# untested - for PT100 platinum thermocouples
#adafruit-circuitpython-max31865
# untested - for mcp9600 and mcp9601
#adafruit-circuitpython-mcp9600