forked from obiben/silvia-pi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsensor.py
145 lines (121 loc) · 4.27 KB
/
sensor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import sys
from time import sleep, time
from math import isnan
from max31865 import max31865
import PID as PID
from config import config as conf
import logging
logger = logging.getLogger('silvia.sensor')
def c_to_f(c):
return c * 9.0 / 5.0 + 32.0
def f_to_c(f):
return (f-32) * 5. / 9.
print(dir(conf))
class Sensor(object):
def __init__(self, state):
self._sensor = max31865(conf.CS, conf.MISO, conf.MOSI, conf.SCK)
self._state = state
self._pid = PID.PID(conf.Pc, conf.Ic, conf.Dc, conf.Sc)
self._pid.SetPoint = self._state['settemp']
self._pid.setSampleTime(conf.sample_time)
self._pid.setWindup(conf.windup)
self._nan_count = 0
self._outlier_count = 0
self._i=0
self._pidhist = [0.,0.,0.,0.,0.,0.,0.,0.,0.,0.]
self._avgpid = 0.
self._temphist = [0.,0.,0.,0.,0.]
self._avgtemp = 0.
self._lastsettemp = state['settemp']
self._lasttime = time()
self._sleeptime = 0
def run(self, verbose=False):
logger.info('PID loop started')
while True:
tempc = self._sensor.readTemp()
if not self.validate_temp(tempc):
if verbose:
print('Invalid temp: {}'.format(tempc))
continue
if self._i == 0:
self._temphist = [tempc for x in range(len(self._temphist))]
temp = self.convert_if_needed(tempc)
self.update_temp_hist_and_average(temp)
self.update_target_temp()
self.update_pid()
self.update_state(temp)
if verbose:
print(self._state)
self.sleep()
def validate_temp(self, tempc):
if isnan(tempc) or tempc == 0.0:
self._nan_count += 1
if self._nan_count > 1000:
self._state['on'] = False
logger.error('100 000 consecutive NaN values from sensor')
raise Exception('100 000 consecutive NaN values from sensor')
return False
else:
if self._i > len(self._temphist) and abs(tempc-self._avgtemp) > 15.0:
self._outlier_count += 1
logger.info('Outlier: {}'.format(tempc))
if self._outlier_count > 50:
logger.error('Outlier count exceeds 50, going to take a nap')
self._state['on'] = False
return True
self._outlier_count = 0
self._nan_count = 0
return True
def convert_if_needed(self, tempc):
if conf.celsius:
temp = tempc
else:
temp = c_to_f(tempc)
return temp
def update_temp_hist_and_average(self, temp):
self._temphist[self._i % len(self._temphist)] = temp
self._avgtemp = sum(self._temphist)/float(len(self._temphist))
def update_target_temp(self):
if self._state['settemp'] != self._lastsettemp:
self._pid.SetPoint = self._state['settemp']
self._lastsettemp = self._state['settemp']
def update_pid(self):
self._pid.update(self._avgtemp)
hist_length = len(self._pidhist)
self._pidhist[self._i % hist_length] = self._pid.output
self._avgpid = sum(self._pidhist) / hist_length
def update_state(self, temp):
self._state['i'] = self._i
self._state['temp'] = round(temp, 2)
self._state['avgtemp'] = round(self._avgtemp, 2)
diff = self._lastsettemp - self._avgtemp
# Be aggressive on machine startup
if diff > 15.0:
self._state['boost'] = time() + 1
# And no need for a PID to tell me whether or not to heat up 1 degree passed our target
if temp == 0.0: # sensor dead
self._state['pidval'] = 0
self._state['avgpid'] = 0
elif diff <= -1:
self._state['pidval'] = 0
self._state['avgpid'] = 0
else:
self._state['pidval'] = min(round(self._pid.output, 2), 999.0) #formatting matters
self._state['avgpid'] = round(self._avgpid, 2)
self._state['pterm'] = round(self._pid.PTerm, 2)
self._state['iterm'] = round(self._pid.ITerm * conf.Ic, 2)
self._state['dterm'] = round(self._pid.DTerm * conf.Dc, 2)
self._state['sterm'] = min(round(self._pid.STerm, 2), 999.0) # Also a question of formatting
def sleep(self):
sleeptime = max(self._lasttime+conf.sample_time-time(), 0)
sleep(sleeptime)
self._i += 1
self._lasttime = time()
def cleanup(self):
self._pid.clear()
# Make sure heating stops if there was a problem
self._state['pidval'] = 0
self._state['avgpid'] = 0
if __name__ == "__main__":
pid = Sensor({ 'settemp': conf.set_temp })
pid.run(verbose=True)