From 6af55eeac679f17a1f1c9a70e53b8889b5bccc4f Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Thu, 29 Apr 2021 23:02:54 +0100 Subject: [PATCH 1/4] simple kiln data logger --- kiln-logger.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/oven.py | 45 +++++++++++++++++-------- requirements.txt | 1 + 3 files changed, 119 insertions(+), 13 deletions(-) create mode 100755 kiln-logger.py diff --git a/kiln-logger.py b/kiln-logger.py new file mode 100755 index 0000000..d030f85 --- /dev/null +++ b/kiln-logger.py @@ -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) diff --git a/lib/oven.py b/lib/oven.py index 4a1f5b0..e2ab839 100644 --- a/lib/oven.py +++ b/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 diff --git a/requirements.txt b/requirements.txt index 91f53f6..dcacab2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ gevent-websocket #RPi.GPIO Adafruit-MAX31855 Adafruit-GPIO +websockets From 3f72206eaaa749871e79f370a8dbf176c48de52f Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Thu, 29 Apr 2021 23:22:04 +0100 Subject: [PATCH 2/4] Fix pip module for websocket client --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dcacab2..6e20584 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,4 @@ gevent-websocket #RPi.GPIO Adafruit-MAX31855 Adafruit-GPIO -websockets +websocket-client From 2dfb4ed61ed3c4a66ad21d2bacc8c94e80bd9a55 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Sun, 16 May 2021 20:31:25 +0100 Subject: [PATCH 3/4] add stdout option --- kiln-logger.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/kiln-logger.py b/kiln-logger.py index d030f85..1d069f4 100755 --- a/kiln-logger.py +++ b/kiln-logger.py @@ -5,6 +5,7 @@ import json import time import csv import argparse +import sys STD_HEADER = [ @@ -37,7 +38,7 @@ PID_HEADER = [ ] -def logger(hostname, csvfile, noprofilestats, pidstats): +def logger(hostname, csvfile, noprofilestats, pidstats, stdout): status_ws = websocket.WebSocket() csv_fields = [] @@ -50,6 +51,12 @@ def logger(hostname, csvfile, noprofilestats, pidstats): csv_out = csv.DictWriter(out, csv_fields, extrasaction='ignore') csv_out.writeheader() + if stdout: + csv_stdout = csv.DictWriter(sys.stdout, csv_fields, extrasaction='ignore', delimiter='\t') + csv_stdout.writeheader() + else: + csv_stdout = None + while True: try: msg = json.loads(status_ws.recv()) @@ -74,6 +81,14 @@ def logger(hostname, csvfile, noprofilestats, pidstats): csv_out.writerow(msg) out.flush() + if stdout: + for k in list(msg.keys()): + v = msg[k] + if isinstance(v, float): + msg[k] = round(v, 3) + csv_stdout.writerow(msg) + sys.stdout.flush() + if __name__ == "__main__": parser = argparse.ArgumentParser(description='Log kiln data for analysis.') @@ -81,6 +96,7 @@ if __name__ == "__main__": 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)") + parser.add_argument('--stdout', action='store_true', help="Also print to stdout") args = parser.parse_args() - logger(args.hostname, args.csvfile, args.noprofilestats, args.pidstats) + logger(args.hostname, args.csvfile, args.noprofilestats, args.pidstats, args.stdout) From 9137834b4139f0cfd8f67801f98eba0b70119e1a Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Tue, 18 May 2021 01:20:19 +0100 Subject: [PATCH 4/4] format stdout a bit better --- kiln-logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiln-logger.py b/kiln-logger.py index 1d069f4..9abdbeb 100755 --- a/kiln-logger.py +++ b/kiln-logger.py @@ -85,7 +85,7 @@ def logger(hostname, csvfile, noprofilestats, pidstats, stdout): for k in list(msg.keys()): v = msg[k] if isinstance(v, float): - msg[k] = round(v, 3) + msg[k] = '{:5.3f}'.format(v) csv_stdout.writerow(msg) sys.stdout.flush()