2013-11-24 16:57:12 +00:00
import threading , time , random , datetime , logging , json
2013-11-29 23:13:06 +00:00
import config
2013-11-23 22:50:59 +00:00
2013-11-23 21:25:20 +00:00
log = logging . getLogger ( __name__ )
2013-11-23 23:36:10 +00:00
try :
from max31855 import MAX31855 , MAX31855Error
2013-11-29 22:27:30 +00:00
sensor_available = True
2013-11-23 23:36:10 +00:00
except ImportError :
log . warning ( " Could not initialize temperature sensor, using dummy values! " )
2013-11-29 22:27:30 +00:00
sensor_available = False
2013-11-23 23:36:10 +00:00
2013-11-29 17:54:51 +00:00
try :
import RPi . GPIO as GPIO
2013-11-29 18:29:11 +00:00
GPIO . setmode ( GPIO . BCM )
2013-11-29 22:30:06 +00:00
GPIO . setwarnings ( False )
2013-11-29 23:13:06 +00:00
GPIO . setup ( config . gpio_heat , GPIO . OUT )
GPIO . setup ( config . gpio_cool , GPIO . OUT )
GPIO . setup ( config . gpio_air , GPIO . OUT )
2013-11-30 12:59:35 +00:00
GPIO . setup ( config . gpio_door , GPIO . IN , pull_up_down = GPIO . PUD_UP )
2013-11-29 23:13:06 +00:00
2013-11-29 22:27:30 +00:00
gpio_available = True
2013-11-29 17:54:51 +00:00
except ImportError :
log . warning ( " Could not initialize GPIOs, oven operation will only be simulated! " )
2013-11-29 22:27:30 +00:00
gpio_available = False
2013-11-29 17:54:51 +00:00
2013-11-23 21:25:20 +00:00
class Oven ( threading . Thread ) :
2013-11-24 14:27:29 +00:00
STATE_IDLE = " IDLE "
2013-11-23 21:25:20 +00:00
STATE_RUNNING = " RUNNING "
2013-11-23 22:40:46 +00:00
2013-12-06 22:02:07 +00:00
def __init__ ( self , simulate = False , time_step = 0.5 ) :
2013-11-23 21:25:20 +00:00
threading . Thread . __init__ ( self )
2013-11-23 22:54:45 +00:00
self . daemon = True
2013-12-06 22:02:07 +00:00
self . simulate = simulate
self . time_step = time_step
2013-11-25 00:50:10 +00:00
self . reset ( )
2013-12-06 22:02:07 +00:00
if simulate :
self . temp_sensor = TempSensorSimulate ( self , 0.5 , self . time_step )
2013-12-01 03:15:58 +00:00
if sensor_available :
2013-12-06 22:02:07 +00:00
self . temp_sensor = TempSensorReal ( self . time_step )
2013-12-01 03:15:58 +00:00
else :
2013-12-06 22:02:07 +00:00
self . temp_sensor = TempSensorSimulate ( self , self . time_step , self . time_step )
2013-11-25 00:50:10 +00:00
self . temp_sensor . start ( )
self . start ( )
2013-11-28 16:44:06 +00:00
2013-11-25 00:50:10 +00:00
def reset ( self ) :
2013-11-23 21:25:20 +00:00
self . profile = None
self . start_time = 0
2013-11-23 23:30:06 +00:00
self . runtime = 0
2013-11-24 14:27:29 +00:00
self . totaltime = 0
2013-11-24 17:35:08 +00:00
self . target = 0
2013-11-30 12:50:56 +00:00
self . door = self . get_door_state ( )
2013-11-23 21:25:20 +00:00
self . state = Oven . STATE_IDLE
2013-11-29 22:27:30 +00:00
self . set_heat ( False )
self . set_cool ( False )
self . set_air ( False )
2013-12-06 22:16:51 +00:00
self . pid = PID ( ki = config . pid_ki , kd = config . pid_kd , kp = config . pid_kp )
2013-11-28 16:44:06 +00:00
2013-11-23 21:25:20 +00:00
def run_profile ( self , profile ) :
2013-11-24 17:35:08 +00:00
log . info ( " Running profile %s " % profile . name )
2013-11-23 21:25:20 +00:00
self . profile = profile
2013-11-24 19:40:09 +00:00
self . totaltime = profile . get_duration ( )
2013-11-23 21:25:20 +00:00
self . state = Oven . STATE_RUNNING
self . start_time = datetime . datetime . now ( )
log . info ( " Starting " )
2013-11-23 22:40:46 +00:00
2013-11-23 21:25:20 +00:00
def abort_run ( self ) :
2013-11-25 00:50:10 +00:00
self . reset ( )
2013-11-23 22:40:46 +00:00
2013-11-23 21:25:20 +00:00
def run ( self ) :
while True :
2013-11-30 12:50:56 +00:00
self . door = self . get_door_state ( )
2013-12-05 19:07:18 +00:00
2013-11-23 21:25:20 +00:00
if self . state == Oven . STATE_RUNNING :
2013-12-06 22:02:07 +00:00
if self . simulate :
self . runtime + = 0.5
else :
self . runtime = ( datetime . datetime . now ( ) - self . start_time ) . total_seconds ( )
2013-11-30 12:48:07 +00:00
log . info ( " running at %.1f deg C (Target: %.1f ) , heat %.2f , cool %.2f , air %.2f , door %s ( %.1f s/ %.0f ) " % ( self . temp_sensor . temperature , self . target , self . heat , self . cool , self . air , self . door , self . runtime , self . totaltime ) )
2013-11-24 17:35:08 +00:00
self . target = self . profile . get_target_temperature ( self . runtime )
2013-12-05 17:31:36 +00:00
pid = self . pid . compute ( self . target , self . temp_sensor . temperature )
2013-12-05 19:07:18 +00:00
2013-12-05 17:31:36 +00:00
log . info ( " pid: %.3f " % pid )
2013-12-05 19:07:18 +00:00
2013-12-05 17:31:36 +00:00
self . set_cool ( pid < = - 1 )
self . set_heat ( pid > 0 )
2013-12-05 19:07:18 +00:00
2013-12-05 17:31:36 +00:00
#if self.profile.is_rising(self.runtime):
# self.set_cool(False)
# self.set_heat(self.temp_sensor.temperature < self.target)
#else:
# self.set_heat(False)
# self.set_cool(self.temp_sensor.temperature > self.target)
2013-12-05 19:07:18 +00:00
2013-12-01 03:15:58 +00:00
if self . temp_sensor . temperature > 200 :
self . set_air ( False )
elif self . temp_sensor . temperature < 180 :
self . set_air ( True )
2013-12-05 19:07:18 +00:00
2013-11-24 14:27:29 +00:00
if self . runtime > = self . totaltime :
2013-11-25 00:50:10 +00:00
self . reset ( )
2013-12-06 22:02:07 +00:00
time . sleep ( self . time_step )
2013-12-05 19:07:18 +00:00
2013-11-29 22:27:30 +00:00
def set_heat ( self , value ) :
if value :
self . heat = 1.0
if gpio_available :
2013-11-29 23:13:06 +00:00
GPIO . output ( config . gpio_heat , GPIO . LOW )
2013-11-29 22:27:30 +00:00
else :
self . heat = 0.0
if gpio_available :
2013-11-29 23:13:06 +00:00
GPIO . output ( config . gpio_heat , GPIO . HIGH )
2013-12-05 19:07:18 +00:00
2013-11-29 22:27:30 +00:00
def set_cool ( self , value ) :
if value :
self . cool = 1.0
if gpio_available :
2013-11-29 23:13:06 +00:00
GPIO . output ( config . gpio_cool , GPIO . LOW )
2013-11-29 22:27:30 +00:00
else :
self . cool = 0.0
if gpio_available :
2013-11-29 23:13:06 +00:00
GPIO . output ( config . gpio_cool , GPIO . HIGH )
2013-12-05 19:07:18 +00:00
2013-11-29 22:27:30 +00:00
def set_air ( self , value ) :
if value :
self . air = 1.0
if gpio_available :
2013-11-29 23:13:06 +00:00
GPIO . output ( config . gpio_air , GPIO . LOW )
2013-11-29 22:27:30 +00:00
else :
self . air = 0.0
if gpio_available :
2013-11-29 23:13:06 +00:00
GPIO . output ( config . gpio_air , GPIO . HIGH )
2013-12-05 19:07:18 +00:00
2013-11-23 21:25:20 +00:00
def get_state ( self ) :
state = {
2013-11-23 23:30:06 +00:00
' runtime ' : self . runtime ,
2013-11-23 21:25:20 +00:00
' temperature ' : self . temp_sensor . temperature ,
2013-11-24 17:35:08 +00:00
' target ' : self . target ,
2013-11-23 21:25:20 +00:00
' state ' : self . state ,
2013-11-29 22:27:30 +00:00
' heat ' : self . heat ,
' cool ' : self . cool ,
' air ' : self . air ,
2013-11-30 12:48:07 +00:00
' totaltime ' : self . totaltime ,
' door ' : self . door
2013-11-23 21:25:20 +00:00
}
return state
2013-12-05 19:07:18 +00:00
2013-11-30 12:50:56 +00:00
def get_door_state ( self ) :
2013-11-30 12:48:07 +00:00
if gpio_available :
2013-11-30 12:50:56 +00:00
return " OPEN " if GPIO . input ( config . gpio_door ) else " CLOSED "
2013-11-30 12:48:07 +00:00
else :
return " UNKNOWN "
2013-12-05 19:07:18 +00:00
2013-11-23 22:40:46 +00:00
2013-11-23 21:25:20 +00:00
class TempSensor ( threading . Thread ) :
2013-12-06 22:02:07 +00:00
def __init__ ( self , time_step ) :
2013-11-23 21:25:20 +00:00
threading . Thread . __init__ ( self )
2013-11-23 22:54:45 +00:00
self . daemon = True
2013-11-23 21:25:20 +00:00
self . temperature = 0
2013-12-06 22:02:07 +00:00
self . time_step = time_step
2013-12-01 03:15:58 +00:00
class TempSensorReal ( TempSensor ) :
2013-12-06 22:02:07 +00:00
def __init__ ( self , time_step ) :
TempSensor . __init__ ( self , time_step )
self . thermocouple = MAX31855 ( config . gpio_sensor_cs ,
config . gpio_sensor_clock ,
config . gpio_sensor_data ,
2013-12-01 03:15:58 +00:00
" c "
)
2013-12-05 19:07:18 +00:00
2013-12-01 03:15:58 +00:00
def run ( self ) :
while True :
self . temperature = self . thermocouple . get ( )
time . sleep ( self . time_step )
2013-12-05 19:07:18 +00:00
2013-12-01 03:15:58 +00:00
class TempSensorSimulate ( TempSensor ) :
2013-12-06 22:02:07 +00:00
def __init__ ( self , oven , time_step , sleep_time ) :
TempSensor . __init__ ( self , time_step )
2013-11-23 21:25:20 +00:00
self . oven = oven
2013-12-06 22:02:07 +00:00
self . sleep_time = sleep_time
2013-12-05 19:07:18 +00:00
2013-11-23 21:25:20 +00:00
def run ( self ) :
2013-12-06 22:16:51 +00:00
t_env = config . sim_t_env
c_heat = config . sim_c_heat
c_oven = config . sim_c_oven
p_heat = config . sim_p_heat
R_o_nocool = config . sim_R_o_nocool
R_o_cool = config . sim_R_o_cool
R_ho_noair = config . sim_R_ho_noair
R_ho_air = config . sim_R_ho_air
2013-12-05 19:07:18 +00:00
2013-12-01 03:15:58 +00:00
t = t_env #deg C temp in oven
t_h = t #deg C temp of heat element
2013-11-23 21:25:20 +00:00
while True :
2013-12-01 03:15:58 +00:00
#heating energy
2013-12-05 19:07:18 +00:00
Q_h = p_heat * self . time_step * self . oven . heat
2013-12-01 03:15:58 +00:00
#temperature change of heat element by heating
2013-12-05 19:07:18 +00:00
t_h + = Q_h / c_heat
2013-12-01 03:15:58 +00:00
if self . oven . air :
R_ho = R_ho_air
2013-11-23 22:50:59 +00:00
else :
2013-12-01 03:15:58 +00:00
R_ho = R_ho_noair
2013-12-05 19:07:18 +00:00
2013-12-01 03:15:58 +00:00
#energy flux heat_el -> oven
p_ho = ( t_h - t ) / R_ho
2013-12-05 19:07:18 +00:00
2013-12-01 03:15:58 +00:00
#temperature change of oven and heat el
t + = p_ho * self . time_step / c_oven
t_h - = p_ho * self . time_step / c_heat
2013-12-05 19:07:18 +00:00
2013-12-01 03:15:58 +00:00
#energy flux oven -> env
if self . oven . cool :
p_env = ( t - t_env ) / R_o_cool
else :
p_env = ( t - t_env ) / R_o_nocool
2013-12-05 19:07:18 +00:00
2013-12-01 03:15:58 +00:00
#temperature change of oven by cooling to env
t - = p_env * self . time_step / c_oven
2013-12-06 23:01:05 +00:00
log . debug ( " energy sim: -> %d W heater: %.0f -> %d W oven: %.0f -> %d W env " % ( int ( p_heat * self . oven . heat ) , t_h , int ( p_ho ) , t , int ( p_env ) ) )
2013-12-01 03:15:58 +00:00
self . temperature = t
2013-12-05 19:07:18 +00:00
2013-12-06 22:02:07 +00:00
time . sleep ( self . sleep_time )
2013-11-23 22:40:46 +00:00
2013-11-24 16:57:12 +00:00
class Profile ( ) :
def __init__ ( self , json_data ) :
obj = json . loads ( json_data )
self . name = obj [ " name " ]
2013-11-24 17:35:08 +00:00
self . data = sorted ( obj [ " data " ] )
2013-11-28 16:44:06 +00:00
2013-11-24 16:57:12 +00:00
def get_duration ( self ) :
return max ( [ t for ( t , x ) in self . data ] )
2013-12-05 19:07:18 +00:00
2013-11-29 22:27:30 +00:00
def get_surrounding_points ( self , time ) :
2013-11-24 17:35:08 +00:00
if time > self . get_duration ( ) :
2013-11-29 22:27:30 +00:00
return ( None , None )
2013-12-05 19:07:18 +00:00
2013-11-24 17:35:08 +00:00
prev_point = None
next_point = None
2013-11-28 16:44:06 +00:00
2013-11-24 17:35:08 +00:00
for i in range ( len ( self . data ) ) :
2013-11-24 17:54:29 +00:00
if time < self . data [ i ] [ 0 ] :
prev_point = self . data [ i - 1 ]
next_point = self . data [ i ]
2013-11-24 17:35:08 +00:00
break
2013-12-05 19:07:18 +00:00
2013-11-29 22:27:30 +00:00
return ( prev_point , next_point )
2013-12-05 19:07:18 +00:00
2013-11-29 22:27:30 +00:00
def is_rising ( self , time ) :
( prev_point , next_point ) = self . get_surrounding_points ( time )
2013-11-29 22:46:21 +00:00
if prev_point and next_point :
return prev_point [ 1 ] < next_point [ 1 ]
else :
return False
2013-12-05 19:07:18 +00:00
2013-11-29 22:27:30 +00:00
def get_target_temperature ( self , time ) :
if time > self . get_duration ( ) :
return 0
2013-11-28 16:44:06 +00:00
2013-11-29 22:27:30 +00:00
( prev_point , next_point ) = self . get_surrounding_points ( time )
2013-12-05 19:07:18 +00:00
2013-11-24 17:54:29 +00:00
incl = float ( next_point [ 1 ] - prev_point [ 1 ] ) / float ( next_point [ 0 ] - prev_point [ 0 ] )
2013-11-24 17:35:08 +00:00
temp = prev_point [ 1 ] + ( time - prev_point [ 0 ] ) * incl
return temp
2013-12-05 17:31:36 +00:00
class PID ( ) :
def __init__ ( self , ki = 1 , kp = 1 , kd = 1 ) :
self . ki = ki
self . kp = kp
self . kd = kd
self . lastNow = datetime . datetime . now ( )
self . iterm = 0
self . lastErr = 0
2013-12-05 19:07:18 +00:00
2013-12-05 17:31:36 +00:00
def compute ( self , setpoint , ispoint ) :
now = datetime . datetime . now ( )
timeDelta = ( now - self . lastNow ) . total_seconds ( )
2013-12-05 19:07:18 +00:00
2013-12-05 17:31:36 +00:00
error = float ( setpoint - ispoint )
self . iterm + = ( error * timeDelta * self . ki )
self . iterm = sorted ( [ - 1 , self . iterm , 1 ] ) [ 1 ]
dErr = ( error - self . lastErr ) / timeDelta
2013-12-05 19:07:18 +00:00
2013-12-05 17:31:36 +00:00
output = self . kp * error + self . iterm + self . kd * dErr
output = sorted ( [ - 1 , output , 1 ] ) [ 1 ]
self . lastErr = error
self . lastNow = now
2013-12-05 19:07:18 +00:00
2013-12-05 17:31:36 +00:00
return output