kopia lustrzana https://github.com/jbruce12000/kiln-controller
simple kiln data logger
rodzic
f1b8cdc91c
commit
6af55eeac6
|
@ -0,0 +1,86 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import websocket
|
||||
import json
|
||||
import time
|
||||
import csv
|
||||
import argparse
|
||||
|
||||
|
||||
STD_HEADER = [
|
||||
'stamp',
|
||||
'runtime',
|
||||
'temperature',
|
||||
'target',
|
||||
'state',
|
||||
'heat',
|
||||
'totaltime',
|
||||
'profile',
|
||||
]
|
||||
|
||||
|
||||
PID_HEADER = [
|
||||
'pid_time',
|
||||
'pid_timeDelta',
|
||||
'pid_setpoint',
|
||||
'pid_ispoint',
|
||||
'pid_err',
|
||||
'pid_errDelta',
|
||||
'pid_p',
|
||||
'pid_i',
|
||||
'pid_d',
|
||||
'pid_kp',
|
||||
'pid_ki',
|
||||
'pid_kd',
|
||||
'pid_pid',
|
||||
'pid_out',
|
||||
]
|
||||
|
||||
|
||||
def logger(hostname, csvfile, noprofilestats, pidstats):
|
||||
status_ws = websocket.WebSocket()
|
||||
|
||||
csv_fields = []
|
||||
if not noprofilestats:
|
||||
csv_fields += STD_HEADER
|
||||
if pidstats:
|
||||
csv_fields += PID_HEADER
|
||||
|
||||
out = open(csvfile, 'w')
|
||||
csv_out = csv.DictWriter(out, csv_fields, extrasaction='ignore')
|
||||
csv_out.writeheader()
|
||||
|
||||
while True:
|
||||
try:
|
||||
msg = json.loads(status_ws.recv())
|
||||
|
||||
except websocket.WebSocketException:
|
||||
try:
|
||||
status_ws.connect(f'ws://{hostname}/status')
|
||||
except Exception:
|
||||
time.sleep(5)
|
||||
|
||||
continue
|
||||
|
||||
if msg.get('type') == 'backlog':
|
||||
continue
|
||||
|
||||
if not noprofilestats:
|
||||
msg['stamp'] = time.time()
|
||||
if pidstats and 'pidstats' in msg:
|
||||
for k, v in msg.get('pidstats', {}).items():
|
||||
msg[f"pid_{k}"] = v
|
||||
|
||||
csv_out.writerow(msg)
|
||||
out.flush()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='Log kiln data for analysis.')
|
||||
parser.add_argument('--hostname', type=str, default="localhost:8081", help="The kiln-controller hostname:port")
|
||||
parser.add_argument('--csvfile', type=str, default="/tmp/kilnstats.csv", help="Where to write the kiln stats to")
|
||||
parser.add_argument('--pidstats', action='store_true', help="Include PID stats")
|
||||
parser.add_argument('--noprofilestats', action='store_true', help="Do not store profile stats (default is to store them)")
|
||||
args = parser.parse_args()
|
||||
|
||||
logger(args.hostname, args.csvfile, args.noprofilestats, args.pidstats)
|
45
lib/oven.py
45
lib/oven.py
|
@ -54,7 +54,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 +64,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,7 +76,7 @@ class Board(object):
|
|||
class BoardSimulated(object):
|
||||
def __init__(self):
|
||||
self.temp_sensor = TempSensorSimulated()
|
||||
|
||||
|
||||
class TempSensor(threading.Thread):
|
||||
def __init__(self):
|
||||
threading.Thread.__init__(self)
|
||||
|
@ -118,7 +118,7 @@ class TempSensorReal(TempSensor):
|
|||
'''take 5 measurements over each time period and return the
|
||||
average'''
|
||||
while True:
|
||||
maxtries = 5
|
||||
maxtries = 5
|
||||
sleeptime = self.time_step / float(maxtries)
|
||||
temps = []
|
||||
for x in range(0,maxtries):
|
||||
|
@ -211,6 +211,7 @@ class Oven(threading.Thread):
|
|||
'heat': self.heat,
|
||||
'totaltime': self.totaltime,
|
||||
'profile': self.profile.name if self.profile else None,
|
||||
'pidstats': self.pid.pidstats,
|
||||
}
|
||||
return state
|
||||
|
||||
|
@ -245,10 +246,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")
|
||||
|
@ -306,9 +307,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)
|
||||
|
||||
|
||||
|
@ -394,9 +395,10 @@ class PID():
|
|||
self.lastNow = datetime.datetime.now()
|
||||
self.iterm = 0
|
||||
self.lastErr = 0
|
||||
self.pidstats = {}
|
||||
|
||||
# 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.
|
||||
|
@ -410,7 +412,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
|
||||
|
@ -427,10 +429,27 @@ class PID():
|
|||
|
||||
output = float(output / window_size)
|
||||
|
||||
if out4logs > 0:
|
||||
self.pidstats = {
|
||||
'time': time.mktime(now.timetuple()),
|
||||
'timeDelta': timeDelta,
|
||||
'setpoint': setpoint,
|
||||
'ispoint': ispoint,
|
||||
'err': error,
|
||||
'errDelta': dErr,
|
||||
'p': self.kp * error,
|
||||
'i': self.iterm,
|
||||
'd': self.kd * dErr,
|
||||
'kp': self.kp,
|
||||
'ki': self.ki,
|
||||
'kd': self.kd,
|
||||
'pid': out4logs,
|
||||
'out': output,
|
||||
}
|
||||
|
||||
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.kp * error)/out4logs)*100,
|
||||
(self.iterm/out4logs)*100,
|
||||
((self.kd * dErr)/out4logs)*100))
|
||||
((self.kd * dErr)/out4logs)*100))
|
||||
|
||||
return output
|
||||
|
|
|
@ -6,3 +6,4 @@ gevent-websocket
|
|||
#RPi.GPIO
|
||||
Adafruit-MAX31855
|
||||
Adafruit-GPIO
|
||||
websockets
|
||||
|
|
Ładowanie…
Reference in New Issue