From 7b15ee004dda66a3001b6379e4fd30a7cc92b97e Mon Sep 17 00:00:00 2001 From: topquark12 Date: Wed, 1 Jun 2022 12:32:18 +0800 Subject: [PATCH 01/17] add pyqtgraph --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index c28f806..588f1ff 100644 --- a/flake.nix +++ b/flake.nix @@ -71,7 +71,7 @@ rustPlatform.rust.cargo openocd dfu-util ] ++ (with python3Packages; [ - numpy matplotlib + numpy matplotlib pyqtgraph ]); }; defaultPackage.x86_64-linux = thermostat; -- 2.44.2 From 07f73bed416a006279795f740b29054e8679bf4c Mon Sep 17 00:00:00 2001 From: topquark12 Date: Wed, 1 Jun 2022 13:09:01 +0800 Subject: [PATCH 02/17] fix pyqtgraph on nixos --- flake.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/flake.nix b/flake.nix index 588f1ff..2701408 100644 --- a/flake.nix +++ b/flake.nix @@ -73,6 +73,11 @@ ] ++ (with python3Packages; [ numpy matplotlib pyqtgraph ]); + shellHook= + '' + export QT_PLUGIN_PATH=${pkgs.qt5.qtbase}/${pkgs.qt5.qtbase.dev.qtPluginPrefix} + export QML2_IMPORT_PATH=${pkgs.qt5.qtbase}/${pkgs.qt5.qtbase.dev.qtQmlPrefix} + ''; }; defaultPackage.x86_64-linux = thermostat; }; -- 2.44.2 From 81cc23a4527b7b9cf320d67d4a430cf7959bd131 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Wed, 1 Jun 2022 17:47:31 +0800 Subject: [PATCH 03/17] plot both channel temperatures --- pytec/tecQT.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 pytec/tecQT.py diff --git a/pytec/tecQT.py b/pytec/tecQT.py new file mode 100644 index 0000000..cf33439 --- /dev/null +++ b/pytec/tecQT.py @@ -0,0 +1,86 @@ +from pyqtgraph.Qt import QtGui, QtCore +import numpy as np +import pyqtgraph as pg +from pytec.client import Client + +rec_len = 1000 +refresh_period = 20 + +channel_data = [{ + 'adc': np.zeros(rec_len), + 'sens': np.zeros(rec_len), + 'temperature': np.zeros(rec_len), + 'i_set': np.zeros(rec_len), + 'pid_output': np.zeros(rec_len), + 'vref': np.zeros(rec_len), + 'dac_value': np.zeros(rec_len), + 'dac_feedback': np.zeros(rec_len), + 'i_tec': np.zeros(rec_len), + 'tec_i': np.zeros(rec_len), + 'tec_u_meas': np.zeros(rec_len), + 'interval': np.zeros(rec_len), + 'temp_set': np.zeros(rec_len), +} for _ in range(2)] + +tec = Client() + +app = pg.mkQApp() +mw = QtGui.QMainWindow() +mw.setWindowTitle('Thermostat Control Panel') +mw.resize(800,800) +cw = QtGui.QWidget() +mw.setCentralWidget(cw) +l = QtGui.QVBoxLayout() +cw.setLayout(l) + +pg.setConfigOptions(antialias=True) + +pw0= pg.PlotWidget(name='Channel 0') +l.addWidget(pw0) +pw1 = pg.PlotWidget(name='Channel 1') +l.addWidget(pw1) + +curve0 = pw0.plot() +curve1 = pw1.plot() + +cnt = 0 +time_stamp = np.zeros(rec_len) +def update(n): + global cnt + for data in tec.report_mode(): + ch = data[n] + for tag, seq in channel_data[n].items(): + if tag in ch: + v = ch[tag] + if type(v) is float: + if cnt == 0: + np.copyto(seq, np.full(rec_len, v)) + else: + seq[:-1] = seq[1:] + seq[-1] = v + if quit: + break + return + +def updateData(): + global cnt + update(0) + update(1) + cnt += 1 + time_stamp[:-1] = time_stamp[1:] + time_stamp[-1] = cnt * refresh_period / 1000 + pw0.setRange(xRange=[cnt * refresh_period / 1000 - 20.0, cnt * refresh_period / 1000]) + pw1.setRange(xRange=[cnt * refresh_period / 1000 - 20.0, cnt * refresh_period / 1000]) + curve0.setData(x = time_stamp, y = channel_data[0]['temperature']) + curve1.setData(x = time_stamp, y = channel_data[1]['temperature']) + + +## Start a timer to rapidly update the plot in pw +t = QtCore.QTimer() +t.timeout.connect(updateData) +t.start(refresh_period) + +mw.show() + +if __name__ == '__main__': + pg.exec() \ No newline at end of file -- 2.44.2 From da8948a166740050d52313867012872c50ef2662 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Thu, 2 Jun 2022 20:08:18 +0800 Subject: [PATCH 04/17] add more graphs in 2x2 grid --- pytec/tecQT.py | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/pytec/tecQT.py b/pytec/tecQT.py index cf33439..aa12fb3 100644 --- a/pytec/tecQT.py +++ b/pytec/tecQT.py @@ -19,7 +19,6 @@ channel_data = [{ 'tec_i': np.zeros(rec_len), 'tec_u_meas': np.zeros(rec_len), 'interval': np.zeros(rec_len), - 'temp_set': np.zeros(rec_len), } for _ in range(2)] tec = Client() @@ -31,22 +30,38 @@ mw.resize(800,800) cw = QtGui.QWidget() mw.setCentralWidget(cw) l = QtGui.QVBoxLayout() +layout = pg.LayoutWidget() +l.addWidget(layout) cw.setLayout(l) pg.setConfigOptions(antialias=True) -pw0= pg.PlotWidget(name='Channel 0') -l.addWidget(pw0) -pw1 = pg.PlotWidget(name='Channel 1') -l.addWidget(pw1) +temp0plot= pg.PlotWidget(title='Channel 0 Temperature') +layout.addWidget(temp0plot, 1, 1) +temp1plot = pg.PlotWidget(title='Channel 1 Temperature') +layout.addWidget(temp1plot, 2, 1) +current0plot = pg.PlotWidget(title='Channel 0 Current') +layout.addWidget(current0plot, 1, 2) +current1plot = pg.PlotWidget(title='Channel 1 Current') +layout.addWidget(current1plot, 2, 2) + +temp0curve = pg.PlotCurveItem(pen=({'color': 'r', 'width': 1})) +temp1curve = pg.PlotCurveItem(pen=({'color': 'r', 'width': 1})) +tecI0curve = pg.PlotCurveItem(pen=({'color': 'r', 'width': 1})) +tecI1curve = pg.PlotCurveItem(pen=({'color': 'r', 'width': 1})) +Iset0curve = pg.PlotCurveItem(pen=({'color': 'g', 'width': 1})) +Iset1curve = pg.PlotCurveItem(pen=({'color': 'g', 'width': 1})) +temp0plot.addItem(temp0curve) +temp1plot.addItem(temp1curve) +current0plot.addItem(tecI0curve) +current0plot.addItem(Iset0curve) +current1plot.addItem(tecI1curve) +current1plot.addItem(Iset1curve) -curve0 = pw0.plot() -curve1 = pw1.plot() cnt = 0 time_stamp = np.zeros(rec_len) def update(n): - global cnt for data in tec.report_mode(): ch = data[n] for tag, seq in channel_data[n].items(): @@ -56,7 +71,7 @@ def update(n): if cnt == 0: np.copyto(seq, np.full(rec_len, v)) else: - seq[:-1] = seq[1:] + seq[:-1] = seq[1:] seq[-1] = v if quit: break @@ -69,10 +84,16 @@ def updateData(): cnt += 1 time_stamp[:-1] = time_stamp[1:] time_stamp[-1] = cnt * refresh_period / 1000 - pw0.setRange(xRange=[cnt * refresh_period / 1000 - 20.0, cnt * refresh_period / 1000]) - pw1.setRange(xRange=[cnt * refresh_period / 1000 - 20.0, cnt * refresh_period / 1000]) - curve0.setData(x = time_stamp, y = channel_data[0]['temperature']) - curve1.setData(x = time_stamp, y = channel_data[1]['temperature']) + temp0plot.setRange(xRange=[(cnt - rec_len) * refresh_period / 1000, cnt * refresh_period / 1000]) + temp1plot.setRange(xRange=[(cnt - rec_len) * refresh_period / 1000, cnt * refresh_period / 1000]) + current0plot.setRange(xRange=[(cnt - rec_len) * refresh_period / 1000, cnt * refresh_period / 1000]) + current1plot.setRange(xRange=[(cnt - rec_len) * refresh_period / 1000, cnt * refresh_period / 1000]) + temp0curve.setData(x = time_stamp, y = channel_data[0]['temperature']) + temp1curve.setData(x = time_stamp, y = channel_data[1]['temperature']) + tecI0curve.setData(x = time_stamp, y = channel_data[0]['tec_i']) + tecI1curve.setData(x = time_stamp, y = channel_data[1]['tec_i']) + Iset0curve.setData(x = time_stamp, y = channel_data[0]['i_set']) + Iset1curve.setData(x = time_stamp, y = channel_data[1]['i_set']) ## Start a timer to rapidly update the plot in pw -- 2.44.2 From 06625d0716d545ba93c339e1a8c11170f8a83641 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Sun, 5 Jun 2022 14:58:42 +0800 Subject: [PATCH 05/17] add graph legends --- pytec/tecQT.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pytec/tecQT.py b/pytec/tecQT.py index aa12fb3..2070392 100644 --- a/pytec/tecQT.py +++ b/pytec/tecQT.py @@ -26,7 +26,7 @@ tec = Client() app = pg.mkQApp() mw = QtGui.QMainWindow() mw.setWindowTitle('Thermostat Control Panel') -mw.resize(800,800) +mw.resize(1024,800) cw = QtGui.QWidget() mw.setCentralWidget(cw) l = QtGui.QVBoxLayout() @@ -58,6 +58,16 @@ current0plot.addItem(Iset0curve) current1plot.addItem(tecI1curve) current1plot.addItem(Iset1curve) +temp0legend = temp0plot.getPlotItem().addLegend(brush=(50,50,200,150)) +temp0legend.addItem(temp0curve, 'feedback') +temp1legend = temp1plot.getPlotItem().addLegend(brush=(50,50,200,150)) +temp1legend.addItem(temp0curve, 'feedback') +current0legend = current0plot.getPlotItem().addLegend(brush=(50,50,200,150)) +current0legend.addItem(tecI0curve, 'feedback') +current0legend.addItem(Iset0curve, 'setpoint') +current1legend = current1plot.getPlotItem().addLegend(brush=(50,50,200,150)) +current1legend.addItem(tecI1curve, 'feedback') +current1legend.addItem(Iset1curve, 'setpoint') cnt = 0 time_stamp = np.zeros(rec_len) -- 2.44.2 From 09f58f42022c9aa40ea96ce2bb8ef43475d56a4d Mon Sep 17 00:00:00 2001 From: topquark12 Date: Sun, 5 Jun 2022 16:03:33 +0800 Subject: [PATCH 06/17] refactor with classes --- pytec/tecQT.py | 131 ++++++++++++++++++++----------------------------- 1 file changed, 53 insertions(+), 78 deletions(-) diff --git a/pytec/tecQT.py b/pytec/tecQT.py index 2070392..1ed6eaf 100644 --- a/pytec/tecQT.py +++ b/pytec/tecQT.py @@ -6,20 +6,43 @@ from pytec.client import Client rec_len = 1000 refresh_period = 20 -channel_data = [{ - 'adc': np.zeros(rec_len), - 'sens': np.zeros(rec_len), - 'temperature': np.zeros(rec_len), - 'i_set': np.zeros(rec_len), - 'pid_output': np.zeros(rec_len), - 'vref': np.zeros(rec_len), - 'dac_value': np.zeros(rec_len), - 'dac_feedback': np.zeros(rec_len), - 'i_tec': np.zeros(rec_len), - 'tec_i': np.zeros(rec_len), - 'tec_u_meas': np.zeros(rec_len), - 'interval': np.zeros(rec_len), -} for _ in range(2)] +class Curves: + def __init__(self, legend: str, key: str, channel: int, color: str, buffer_len: int, period: int): + self.curveItem = pg.PlotCurveItem(pen=({'color': color, 'width': 1})) + self.legendStr = legend + self.keyStr = key + self.channel = channel + self.data_buf = np.zeros(buffer_len) + self.time_stamp = np.zeros(buffer_len) + self.buffLen = buffer_len + self.period = period + + def update(self, tec_data, cnt): + if cnt == 0: + np.copyto(self.data_buf, np.full(self.buffLen, tec_data[self.channel][self.keyStr])) + else: + self.data_buf[:-1] = self.data_buf[1:] + self.data_buf[-1] = tec_data[self.channel][self.keyStr] + self.time_stamp[:-1] = self.time_stamp[1:] + self.time_stamp[-1] = cnt * self.period / 1000 + self.curveItem.setData(x = self.time_stamp, y = self.data_buf) + + +class Graph: + def __init__(self, parent: pg.LayoutWidget, title: str, row: int, col: int, curves: list[Curves]): + self.plotItem = pg.PlotWidget(title=title) + self.legendItem = pg.LegendItem(offset=(75, 30), brush=(50,50,200,150)) + self.legendItem.setParentItem(self.plotItem.getPlotItem()) + parent.addWidget(self.plotItem, row, col) + self.curves = curves + for curve in self.curves: + self.plotItem.addItem(curve.curveItem) + self.legendItem.addItem(curve.curveItem, curve.legendStr) + + def update(self, tec_data, cnt): + for curve in self.curves: + curve.update(tec_data, cnt) + self.plotItem.setRange(xRange=[(cnt - self.curves[0].buffLen) * self.curves[0].period / 1000, cnt * self.curves[0].period / 1000]) tec = Client() @@ -36,74 +59,26 @@ cw.setLayout(l) pg.setConfigOptions(antialias=True) -temp0plot= pg.PlotWidget(title='Channel 0 Temperature') -layout.addWidget(temp0plot, 1, 1) -temp1plot = pg.PlotWidget(title='Channel 1 Temperature') -layout.addWidget(temp1plot, 2, 1) -current0plot = pg.PlotWidget(title='Channel 0 Current') -layout.addWidget(current0plot, 1, 2) -current1plot = pg.PlotWidget(title='Channel 1 Current') -layout.addWidget(current1plot, 2, 2) - -temp0curve = pg.PlotCurveItem(pen=({'color': 'r', 'width': 1})) -temp1curve = pg.PlotCurveItem(pen=({'color': 'r', 'width': 1})) -tecI0curve = pg.PlotCurveItem(pen=({'color': 'r', 'width': 1})) -tecI1curve = pg.PlotCurveItem(pen=({'color': 'r', 'width': 1})) -Iset0curve = pg.PlotCurveItem(pen=({'color': 'g', 'width': 1})) -Iset1curve = pg.PlotCurveItem(pen=({'color': 'g', 'width': 1})) -temp0plot.addItem(temp0curve) -temp1plot.addItem(temp1curve) -current0plot.addItem(tecI0curve) -current0plot.addItem(Iset0curve) -current1plot.addItem(tecI1curve) -current1plot.addItem(Iset1curve) - -temp0legend = temp0plot.getPlotItem().addLegend(brush=(50,50,200,150)) -temp0legend.addItem(temp0curve, 'feedback') -temp1legend = temp1plot.getPlotItem().addLegend(brush=(50,50,200,150)) -temp1legend.addItem(temp0curve, 'feedback') -current0legend = current0plot.getPlotItem().addLegend(brush=(50,50,200,150)) -current0legend.addItem(tecI0curve, 'feedback') -current0legend.addItem(Iset0curve, 'setpoint') -current1legend = current1plot.getPlotItem().addLegend(brush=(50,50,200,150)) -current1legend.addItem(tecI1curve, 'feedback') -current1legend.addItem(Iset1curve, 'setpoint') +ch0tempGraph = Graph(layout, 'Channel 0 Termperature', 1, 1, [Curves('Feedback', 'temperature', 0, 'r', rec_len, refresh_period)]) +ch1tempGraph = Graph(layout, 'Channel 1 Termperature', 2, 1, [Curves('Feedback', 'temperature', 1, 'r', rec_len, refresh_period)]) +ch0currentGraph = Graph(layout, 'Channel 0 Current', 1, 2, [Curves('Feedback', 'tec_i', 0, 'r', rec_len, refresh_period), + Curves('Setpoint', 'i_set', 0, 'g', rec_len, refresh_period)]) +ch1currentGraph = Graph(layout, 'Channel 1 Current', 2, 2, [Curves('Feedback', 'tec_i', 1, 'r', rec_len, refresh_period), + Curves('Setpoint', 'i_set', 1, 'g', rec_len, refresh_period)]) cnt = 0 -time_stamp = np.zeros(rec_len) -def update(n): - for data in tec.report_mode(): - ch = data[n] - for tag, seq in channel_data[n].items(): - if tag in ch: - v = ch[tag] - if type(v) is float: - if cnt == 0: - np.copyto(seq, np.full(rec_len, v)) - else: - seq[:-1] = seq[1:] - seq[-1] = v - if quit: - break - return - def updateData(): global cnt - update(0) - update(1) - cnt += 1 - time_stamp[:-1] = time_stamp[1:] - time_stamp[-1] = cnt * refresh_period / 1000 - temp0plot.setRange(xRange=[(cnt - rec_len) * refresh_period / 1000, cnt * refresh_period / 1000]) - temp1plot.setRange(xRange=[(cnt - rec_len) * refresh_period / 1000, cnt * refresh_period / 1000]) - current0plot.setRange(xRange=[(cnt - rec_len) * refresh_period / 1000, cnt * refresh_period / 1000]) - current1plot.setRange(xRange=[(cnt - rec_len) * refresh_period / 1000, cnt * refresh_period / 1000]) - temp0curve.setData(x = time_stamp, y = channel_data[0]['temperature']) - temp1curve.setData(x = time_stamp, y = channel_data[1]['temperature']) - tecI0curve.setData(x = time_stamp, y = channel_data[0]['tec_i']) - tecI1curve.setData(x = time_stamp, y = channel_data[1]['tec_i']) - Iset0curve.setData(x = time_stamp, y = channel_data[0]['i_set']) - Iset1curve.setData(x = time_stamp, y = channel_data[1]['i_set']) + for data in tec.report_mode(): + + ch0tempGraph.update(data, cnt) + ch1tempGraph.update(data, cnt) + ch0currentGraph.update(data, cnt) + ch1currentGraph.update(data, cnt) + + if quit: + break + cnt += 1 ## Start a timer to rapidly update the plot in pw -- 2.44.2 From 1d8bd9903813e3300ef9bb0eb5c365d0e9241eed Mon Sep 17 00:00:00 2001 From: topquark12 Date: Sun, 5 Jun 2022 16:04:46 +0800 Subject: [PATCH 07/17] fix typo --- pytec/tecQT.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytec/tecQT.py b/pytec/tecQT.py index 1ed6eaf..df8f403 100644 --- a/pytec/tecQT.py +++ b/pytec/tecQT.py @@ -59,8 +59,8 @@ cw.setLayout(l) pg.setConfigOptions(antialias=True) -ch0tempGraph = Graph(layout, 'Channel 0 Termperature', 1, 1, [Curves('Feedback', 'temperature', 0, 'r', rec_len, refresh_period)]) -ch1tempGraph = Graph(layout, 'Channel 1 Termperature', 2, 1, [Curves('Feedback', 'temperature', 1, 'r', rec_len, refresh_period)]) +ch0tempGraph = Graph(layout, 'Channel 0 Temperature', 1, 1, [Curves('Feedback', 'temperature', 0, 'r', rec_len, refresh_period)]) +ch1tempGraph = Graph(layout, 'Channel 1 Temperature', 2, 1, [Curves('Feedback', 'temperature', 1, 'r', rec_len, refresh_period)]) ch0currentGraph = Graph(layout, 'Channel 0 Current', 1, 2, [Curves('Feedback', 'tec_i', 0, 'r', rec_len, refresh_period), Curves('Setpoint', 'i_set', 0, 'g', rec_len, refresh_period)]) ch1currentGraph = Graph(layout, 'Channel 1 Current', 2, 2, [Curves('Feedback', 'tec_i', 1, 'r', rec_len, refresh_period), -- 2.44.2 From da415e6da6c3f68b2a6f84b85ee4fab605490e5a Mon Sep 17 00:00:00 2001 From: topquark12 Date: Sun, 5 Jun 2022 17:21:50 +0800 Subject: [PATCH 08/17] add voltage monitoring --- pytec/tecQT.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pytec/tecQT.py b/pytec/tecQT.py index df8f403..7bce9f0 100644 --- a/pytec/tecQT.py +++ b/pytec/tecQT.py @@ -3,6 +3,8 @@ import numpy as np import pyqtgraph as pg from pytec.client import Client +tec = Client(host="192.168.1.26", port=23, timeout=None) + rec_len = 1000 refresh_period = 20 @@ -27,7 +29,6 @@ class Curves: self.time_stamp[-1] = cnt * self.period / 1000 self.curveItem.setData(x = self.time_stamp, y = self.data_buf) - class Graph: def __init__(self, parent: pg.LayoutWidget, title: str, row: int, col: int, curves: list[Curves]): self.plotItem = pg.PlotWidget(title=title) @@ -44,12 +45,10 @@ class Graph: curve.update(tec_data, cnt) self.plotItem.setRange(xRange=[(cnt - self.curves[0].buffLen) * self.curves[0].period / 1000, cnt * self.curves[0].period / 1000]) -tec = Client() - app = pg.mkQApp() mw = QtGui.QMainWindow() mw.setWindowTitle('Thermostat Control Panel') -mw.resize(1024,800) +mw.resize(1500,800) cw = QtGui.QWidget() mw.setCentralWidget(cw) l = QtGui.QVBoxLayout() @@ -59,12 +58,14 @@ cw.setLayout(l) pg.setConfigOptions(antialias=True) -ch0tempGraph = Graph(layout, 'Channel 0 Temperature', 1, 1, [Curves('Feedback', 'temperature', 0, 'r', rec_len, refresh_period)]) -ch1tempGraph = Graph(layout, 'Channel 1 Temperature', 2, 1, [Curves('Feedback', 'temperature', 1, 'r', rec_len, refresh_period)]) -ch0currentGraph = Graph(layout, 'Channel 0 Current', 1, 2, [Curves('Feedback', 'tec_i', 0, 'r', rec_len, refresh_period), +ch0tempGraph = Graph(layout, 'Channel 0 Temperature', 1, 2, [Curves('Feedback', 'temperature', 0, 'r', rec_len, refresh_period)]) +ch1tempGraph = Graph(layout, 'Channel 1 Temperature', 2, 2, [Curves('Feedback', 'temperature', 1, 'r', rec_len, refresh_period)]) +ch0currentGraph = Graph(layout, 'Channel 0 Current', 1, 3, [Curves('Feedback', 'tec_i', 0, 'r', rec_len, refresh_period), Curves('Setpoint', 'i_set', 0, 'g', rec_len, refresh_period)]) -ch1currentGraph = Graph(layout, 'Channel 1 Current', 2, 2, [Curves('Feedback', 'tec_i', 1, 'r', rec_len, refresh_period), +ch1currentGraph = Graph(layout, 'Channel 1 Current', 2, 3, [Curves('Feedback', 'tec_i', 1, 'r', rec_len, refresh_period), Curves('Setpoint', 'i_set', 1, 'g', rec_len, refresh_period)]) +ch0voltGraph = Graph(layout, 'Channel 0 Voltage', 1, 4, [Curves('Feedback', 'tec_u_meas', 0, 'r', rec_len, refresh_period)]) +ch1voltGraph = Graph(layout, 'Channel 1 Voltage', 2, 4, [Curves('Feedback', 'tec_u_meas', 1, 'r', rec_len, refresh_period)]) cnt = 0 def updateData(): @@ -75,13 +76,13 @@ def updateData(): ch1tempGraph.update(data, cnt) ch0currentGraph.update(data, cnt) ch1currentGraph.update(data, cnt) + ch0voltGraph.update(data, cnt) + ch1voltGraph.update(data, cnt) if quit: break cnt += 1 - -## Start a timer to rapidly update the plot in pw t = QtCore.QTimer() t.timeout.connect(updateData) t.start(refresh_period) -- 2.44.2 From 1083af12660ebc62a7e97dc5375de1298123cb85 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Sun, 5 Jun 2022 18:59:05 +0800 Subject: [PATCH 09/17] add param tree, param tree inactive --- pytec/tecQT.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/pytec/tecQT.py b/pytec/tecQT.py index 7bce9f0..92c0306 100644 --- a/pytec/tecQT.py +++ b/pytec/tecQT.py @@ -1,4 +1,6 @@ from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.parametertree.parameterTypes as pTypes +from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType import numpy as np import pyqtgraph as pg from pytec.client import Client @@ -8,6 +10,64 @@ tec = Client(host="192.168.1.26", port=23, timeout=None) rec_len = 1000 refresh_period = 20 +# Channel 0 or 1 +# +# |- Output enable +# |- Set Constant Current (Disables Constant Temperature) +# |- Set Constant Temperature (Disables Constant Current) +# |- Output Config +# |- Max Current +# |- Max Voltage +# |- Thermistor Config +# |- T0 +# |- R0 +# |- Beta +# |- PID Config +# |- kP +# |- kI +# |- kD +# |- (Auto Tune PID) +# (Save Configs) + +params = [[ + {'name': 'Enable Output', 'type': 'bool', 'value': False}, + {'name': 'Enable Constant Current', 'type': 'bool', 'value': False, 'children': [ + {'name': 'Set Current', 'type': 'float', 'value': 0, 'step': 0.1, 'siPrefix': True, 'suffix': 'A'}, + ]}, + {'name': 'Enable PID', 'type': 'bool', 'value': False, 'children': [ + {'name': 'Set Temperature', 'type': 'float', 'value': 25, 'step': 0.1, 'siPrefix': True, 'suffix': 'C'}, + ]}, + {'name': 'Output Config', 'type': 'group', 'children': [ + {'name': 'Max Current', 'type': 'float', 'value': 0, 'step': 0.1, 'siPrefix': True, 'suffix': 'A'}, + {'name': 'Max Voltage', 'type': 'float', 'value': 0, 'step': 0.1, 'siPrefix': True, 'suffix': 'V'}, + ]}, + {'name': 'Thermistor Config', 'type': 'group', 'children': [ + {'name': 'T0', 'type': 'float', 'value': 25, 'step': 0.1, 'siPrefix': True, 'suffix': 'C'}, + {'name': 'R0', 'type': 'float', 'value': 10000, 'step': 1, 'siPrefix': True, 'suffix': 'Ohm'}, + {'name': 'Beta', 'type': 'float', 'value': 3950, 'step': 1}, + ]}, + {'name': 'PID Config', 'type': 'group', 'children': [ + {'name': 'kP', 'type': 'float', 'value': 0, 'step': 0.1}, + {'name': 'kI', 'type': 'float', 'value': 0, 'step': 0.1}, + {'name': 'kD', 'type': 'float', 'value': 0, 'step': 0.1}, + ]}, + {'name': 'Save', 'type': 'action', 'tip': 'Save'}, +] for _ in range(2)] + +## If anything changes in the tree, print a message +def change(param, changes): + print("tree changes:") + for param, change, data in changes: + path = paramList0.childPath(param) + if path is not None: + childName = '.'.join(path) + else: + childName = param.name() + print(' parameter: %s'% childName) + print(' change: %s'% change) + print(' data: %s'% str(data)) + print(' ----------') + class Curves: def __init__(self, legend: str, key: str, channel: int, color: str, buffer_len: int, period: int): self.curveItem = pg.PlotCurveItem(pen=({'color': color, 'width': 1})) @@ -46,9 +106,10 @@ class Graph: self.plotItem.setRange(xRange=[(cnt - self.curves[0].buffLen) * self.curves[0].period / 1000, cnt * self.curves[0].period / 1000]) app = pg.mkQApp() +pg.setConfigOptions(antialias=True) mw = QtGui.QMainWindow() mw.setWindowTitle('Thermostat Control Panel') -mw.resize(1500,800) +mw.resize(1920,1200) cw = QtGui.QWidget() mw.setCentralWidget(cw) l = QtGui.QVBoxLayout() @@ -56,7 +117,19 @@ layout = pg.LayoutWidget() l.addWidget(layout) cw.setLayout(l) -pg.setConfigOptions(antialias=True) +## Create tree of Parameter objects +paramList0 = Parameter.create(name='params', type='group', children=params[0]) +paramList0.sigTreeStateChanged.connect(change) +ch0Tree = ParameterTree() +ch0Tree.setParameters(paramList0, showTop=False) + +paramList1 = Parameter.create(name='params', type='group', children=params[1]) +paramList1.sigTreeStateChanged.connect(change) +ch1Tree = ParameterTree() +ch1Tree.setParameters(paramList1, showTop=False) + +layout.addWidget(ch0Tree, 1, 1, 1, 1) +layout.addWidget(ch1Tree, 2, 1, 1, 1) ch0tempGraph = Graph(layout, 'Channel 0 Temperature', 1, 2, [Curves('Feedback', 'temperature', 0, 'r', rec_len, refresh_period)]) ch1tempGraph = Graph(layout, 'Channel 1 Temperature', 2, 2, [Curves('Feedback', 'temperature', 1, 'r', rec_len, refresh_period)]) @@ -64,8 +137,6 @@ ch0currentGraph = Graph(layout, 'Channel 0 Current', 1, 3, [Curves('Feedback', ' Curves('Setpoint', 'i_set', 0, 'g', rec_len, refresh_period)]) ch1currentGraph = Graph(layout, 'Channel 1 Current', 2, 3, [Curves('Feedback', 'tec_i', 1, 'r', rec_len, refresh_period), Curves('Setpoint', 'i_set', 1, 'g', rec_len, refresh_period)]) -ch0voltGraph = Graph(layout, 'Channel 0 Voltage', 1, 4, [Curves('Feedback', 'tec_u_meas', 0, 'r', rec_len, refresh_period)]) -ch1voltGraph = Graph(layout, 'Channel 1 Voltage', 2, 4, [Curves('Feedback', 'tec_u_meas', 1, 'r', rec_len, refresh_period)]) cnt = 0 def updateData(): @@ -76,8 +147,6 @@ def updateData(): ch1tempGraph.update(data, cnt) ch0currentGraph.update(data, cnt) ch1currentGraph.update(data, cnt) - ch0voltGraph.update(data, cnt) - ch1voltGraph.update(data, cnt) if quit: break -- 2.44.2 From d74e806de8cc272b09474c460acb92c89cab2af1 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Mon, 6 Jun 2022 12:38:44 +0800 Subject: [PATCH 10/17] add sync from TEC --- pytec/tecQT.py | 153 ++++++++++++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 59 deletions(-) diff --git a/pytec/tecQT.py b/pytec/tecQT.py index 92c0306..8b07ff4 100644 --- a/pytec/tecQT.py +++ b/pytec/tecQT.py @@ -5,31 +5,38 @@ import numpy as np import pyqtgraph as pg from pytec.client import Client -tec = Client(host="192.168.1.26", port=23, timeout=None) - rec_len = 1000 refresh_period = 20 -# Channel 0 or 1 -# -# |- Output enable -# |- Set Constant Current (Disables Constant Temperature) -# |- Set Constant Temperature (Disables Constant Current) -# |- Output Config -# |- Max Current -# |- Max Voltage -# |- Thermistor Config -# |- T0 -# |- R0 -# |- Beta -# |- PID Config -# |- kP -# |- kI -# |- kD -# |- (Auto Tune PID) -# (Save Configs) +TECparams = [ [ + {'tag': 'report', 'type': 'parent', 'children': [ + {'tag': 'pid_engaged', 'type': 'bool', 'value': False}, + ]}, + {'tag': 'pwm', 'type': 'parent', 'children': [ + {'tag': 'max_i_pos', 'type': 'float', 'value': 0}, + {'tag': 'max_i_neg', 'type': 'float', 'value': 0}, + {'tag': 'max_v', 'type': 'float', 'value': 0}, + {'tag': 'i_set', 'type': 'float', 'value': 0}, + ]}, + {'tag': 'pid', 'type': 'parent', 'children': [ + {'tag': 'kp', 'type': 'float', 'value': 0}, + {'tag': 'ki', 'type': 'float', 'value': 0}, + {'tag': 'kd', 'type': 'float', 'value': 0}, + {'tag': 'output_min', 'type': 'float', 'value': 0}, + {'tag': 'output_max', 'type': 'float', 'value': 0}, + ]}, + {'tag': 's-h', 'type': 'parent', 'children': [ + {'tag': 't0', 'type': 'float', 'value': 0}, + {'tag': 'r0', 'type': 'float', 'value': 0}, + {'tag': 'b', 'type': 'float', 'value': 0}, + ]}, + {'tag': 'PIDtarget', 'type': 'parent', 'children': [ + {'tag': 'target', 'type': 'float', 'value': 0}, + ]}, +] for _ in range(2)] -params = [[ + +GUIparams = [[ {'name': 'Enable Output', 'type': 'bool', 'value': False}, {'name': 'Enable Constant Current', 'type': 'bool', 'value': False, 'children': [ {'name': 'Set Current', 'type': 'float', 'value': 0, 'step': 0.1, 'siPrefix': True, 'suffix': 'A'}, @@ -52,7 +59,7 @@ params = [[ {'name': 'kD', 'type': 'float', 'value': 0, 'step': 0.1}, ]}, {'name': 'Save', 'type': 'action', 'tip': 'Save'}, -] for _ in range(2)] +] for ch in range(2)] ## If anything changes in the tree, print a message def change(param, changes): @@ -105,38 +112,29 @@ class Graph: curve.update(tec_data, cnt) self.plotItem.setRange(xRange=[(cnt - self.curves[0].buffLen) * self.curves[0].period / 1000, cnt * self.curves[0].period / 1000]) -app = pg.mkQApp() -pg.setConfigOptions(antialias=True) -mw = QtGui.QMainWindow() -mw.setWindowTitle('Thermostat Control Panel') -mw.resize(1920,1200) -cw = QtGui.QWidget() -mw.setCentralWidget(cw) -l = QtGui.QVBoxLayout() -layout = pg.LayoutWidget() -l.addWidget(layout) -cw.setLayout(l) +def TECsync(): + global TECparams + for channel in range(2): + for parents in TECparams[channel]: + if parents['tag'] == 'report': + for data in tec.report_mode(): + for children in parents['children']: + children['value'] = data[channel][children['tag']] + if quit: + break + if parents['tag'] == 'pwm': + for children in parents['children']: + children['value'] = tec.get_pwm()[channel][children['tag']]['value'] + if parents['tag'] == 'pid': + for children in parents['children']: + children['value'] = tec.get_pid()[channel]['parameters'][children['tag']] + if parents['tag'] == 's-h': + for children in parents['children']: + children['value'] = tec.get_steinhart_hart()[channel]['params'][children['tag']] + if parents['tag'] == 'PIDtarget': + for children in parents['children']: + children['value'] = tec.get_pid()[channel]['target'] -## Create tree of Parameter objects -paramList0 = Parameter.create(name='params', type='group', children=params[0]) -paramList0.sigTreeStateChanged.connect(change) -ch0Tree = ParameterTree() -ch0Tree.setParameters(paramList0, showTop=False) - -paramList1 = Parameter.create(name='params', type='group', children=params[1]) -paramList1.sigTreeStateChanged.connect(change) -ch1Tree = ParameterTree() -ch1Tree.setParameters(paramList1, showTop=False) - -layout.addWidget(ch0Tree, 1, 1, 1, 1) -layout.addWidget(ch1Tree, 2, 1, 1, 1) - -ch0tempGraph = Graph(layout, 'Channel 0 Temperature', 1, 2, [Curves('Feedback', 'temperature', 0, 'r', rec_len, refresh_period)]) -ch1tempGraph = Graph(layout, 'Channel 1 Temperature', 2, 2, [Curves('Feedback', 'temperature', 1, 'r', rec_len, refresh_period)]) -ch0currentGraph = Graph(layout, 'Channel 0 Current', 1, 3, [Curves('Feedback', 'tec_i', 0, 'r', rec_len, refresh_period), - Curves('Setpoint', 'i_set', 0, 'g', rec_len, refresh_period)]) -ch1currentGraph = Graph(layout, 'Channel 1 Current', 2, 3, [Curves('Feedback', 'tec_i', 1, 'r', rec_len, refresh_period), - Curves('Setpoint', 'i_set', 1, 'g', rec_len, refresh_period)]) cnt = 0 def updateData(): @@ -152,11 +150,48 @@ def updateData(): break cnt += 1 -t = QtCore.QTimer() -t.timeout.connect(updateData) -t.start(refresh_period) - -mw.show() - + if __name__ == '__main__': + tec = Client(host="192.168.1.26", port=23, timeout=None) + TECsync() + + app = pg.mkQApp() + pg.setConfigOptions(antialias=True) + mw = QtGui.QMainWindow() + mw.setWindowTitle('Thermostat Control Panel') + mw.resize(1920,1200) + cw = QtGui.QWidget() + mw.setCentralWidget(cw) + l = QtGui.QVBoxLayout() + layout = pg.LayoutWidget() + l.addWidget(layout) + cw.setLayout(l) + + ## Create tree of Parameter objects + paramList0 = Parameter.create(name='GUIparams', type='group', children=GUIparams[0]) + paramList0.sigTreeStateChanged.connect(change) + ch0Tree = ParameterTree() + ch0Tree.setParameters(paramList0, showTop=False) + + paramList1 = Parameter.create(name='GUIparams', type='group', children=GUIparams[1]) + paramList1.sigTreeStateChanged.connect(change) + ch1Tree = ParameterTree() + ch1Tree.setParameters(paramList1, showTop=False) + + layout.addWidget(ch0Tree, 1, 1, 1, 1) + layout.addWidget(ch1Tree, 2, 1, 1, 1) + + ch0tempGraph = Graph(layout, 'Channel 0 Temperature', 1, 2, [Curves('Feedback', 'temperature', 0, 'r', rec_len, refresh_period)]) + ch1tempGraph = Graph(layout, 'Channel 1 Temperature', 2, 2, [Curves('Feedback', 'temperature', 1, 'r', rec_len, refresh_period)]) + ch0currentGraph = Graph(layout, 'Channel 0 Current', 1, 3, [Curves('Feedback', 'tec_i', 0, 'r', rec_len, refresh_period), + Curves('Setpoint', 'i_set', 0, 'g', rec_len, refresh_period)]) + ch1currentGraph = Graph(layout, 'Channel 1 Current', 2, 3, [Curves('Feedback', 'tec_i', 1, 'r', rec_len, refresh_period), + Curves('Setpoint', 'i_set', 1, 'g', rec_len, refresh_period)]) + + t = QtCore.QTimer() + t.timeout.connect(updateData) + t.start(refresh_period) + + mw.show() + pg.exec() \ No newline at end of file -- 2.44.2 From 24cc7dd2b746de9eac7c211b997b56708ed0e9ea Mon Sep 17 00:00:00 2001 From: topquark12 Date: Mon, 6 Jun 2022 13:49:58 +0800 Subject: [PATCH 11/17] sync tree param from TEC --- pytec/tecQT.py | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/pytec/tecQT.py b/pytec/tecQT.py index 8b07ff4..163a4e0 100644 --- a/pytec/tecQT.py +++ b/pytec/tecQT.py @@ -37,29 +37,29 @@ TECparams = [ [ GUIparams = [[ - {'name': 'Enable Output', 'type': 'bool', 'value': False}, - {'name': 'Enable Constant Current', 'type': 'bool', 'value': False, 'children': [ + {'name': 'Disable Output', 'type': 'action', 'tip': 'Disable Output'}, + {'name': 'Constant Current', 'type': 'bool', 'value': False, 'children': [ {'name': 'Set Current', 'type': 'float', 'value': 0, 'step': 0.1, 'siPrefix': True, 'suffix': 'A'}, ]}, - {'name': 'Enable PID', 'type': 'bool', 'value': False, 'children': [ + {'name': 'Temperature PID', 'type': 'bool', 'value': False, 'children': [ {'name': 'Set Temperature', 'type': 'float', 'value': 25, 'step': 0.1, 'siPrefix': True, 'suffix': 'C'}, ]}, - {'name': 'Output Config', 'type': 'group', 'children': [ + {'name': 'Output Config', 'expanded': False, 'type': 'group', 'children': [ {'name': 'Max Current', 'type': 'float', 'value': 0, 'step': 0.1, 'siPrefix': True, 'suffix': 'A'}, {'name': 'Max Voltage', 'type': 'float', 'value': 0, 'step': 0.1, 'siPrefix': True, 'suffix': 'V'}, ]}, - {'name': 'Thermistor Config', 'type': 'group', 'children': [ + {'name': 'Thermistor Config', 'expanded': False, 'type': 'group', 'children': [ {'name': 'T0', 'type': 'float', 'value': 25, 'step': 0.1, 'siPrefix': True, 'suffix': 'C'}, {'name': 'R0', 'type': 'float', 'value': 10000, 'step': 1, 'siPrefix': True, 'suffix': 'Ohm'}, {'name': 'Beta', 'type': 'float', 'value': 3950, 'step': 1}, ]}, - {'name': 'PID Config', 'type': 'group', 'children': [ + {'name': 'PID Config', 'expanded': False, 'type': 'group', 'children': [ {'name': 'kP', 'type': 'float', 'value': 0, 'step': 0.1}, {'name': 'kI', 'type': 'float', 'value': 0, 'step': 0.1}, {'name': 'kD', 'type': 'float', 'value': 0, 'step': 0.1}, ]}, {'name': 'Save', 'type': 'action', 'tip': 'Save'}, -] for ch in range(2)] +] for _ in range(2)] ## If anything changes in the tree, print a message def change(param, changes): @@ -134,7 +134,23 @@ def TECsync(): if parents['tag'] == 'PIDtarget': for children in parents['children']: children['value'] = tec.get_pid()[channel]['target'] - + +def refreshTreeParam(tempTree:dict, channel:int) -> dict: + print(tempTree) + print(type(tempTree)) + tempTree['children']['Constant Current']['value'] = not TECparams[channel][0]['children'][0]['value'] + tempTree['children']['Constant Current']['children']['Set Current']['value'] = TECparams[channel][1]['children'][3]['value'] + tempTree['children']['Temperature PID']['value'] = TECparams[channel][0]['children'][0]['value'] + tempTree['children']['Temperature PID']['children']['Set Temperature']['value'] = TECparams[channel][4]['children'][0]['value'] + tempTree['children']['Output Config']['children']['Max Current']['value'] = TECparams[channel][1]['children'][0]['value'] + tempTree['children']['Output Config']['children']['Max Voltage']['value'] = TECparams[channel][1]['children'][2]['value'] + tempTree['children']['Thermistor Config']['children']['T0']['value'] = TECparams[channel][3]['children'][0]['value'] - 273.15 + tempTree['children']['Thermistor Config']['children']['R0']['value'] = TECparams[channel][3]['children'][1]['value'] + tempTree['children']['Thermistor Config']['children']['Beta']['value'] = TECparams[channel][3]['children'][2]['value'] + tempTree['children']['PID Config']['children']['kP']['value'] = TECparams[channel][2]['children'][0]['value'] + tempTree['children']['PID Config']['children']['kI']['value'] = TECparams[channel][2]['children'][1]['value'] + tempTree['children']['PID Config']['children']['kD']['value'] = TECparams[channel][2]['children'][2]['value'] + return tempTree cnt = 0 def updateData(): @@ -150,10 +166,9 @@ def updateData(): break cnt += 1 - + if __name__ == '__main__': tec = Client(host="192.168.1.26", port=23, timeout=None) - TECsync() app = pg.mkQApp() pg.setConfigOptions(antialias=True) @@ -178,6 +193,10 @@ if __name__ == '__main__': ch1Tree = ParameterTree() ch1Tree.setParameters(paramList1, showTop=False) + TECsync() + paramList0.restoreState(refreshTreeParam(paramList0.saveState(), 0)) + paramList1.restoreState(refreshTreeParam(paramList1.saveState(), 1)) + layout.addWidget(ch0Tree, 1, 1, 1, 1) layout.addWidget(ch1Tree, 2, 1, 1, 1) -- 2.44.2 From cdb78094ca771c9a36772008a531c0fcfe3a2530 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Mon, 6 Jun 2022 15:24:58 +0800 Subject: [PATCH 12/17] bi-dir sync, minimum working prototype --- pytec/tecQT.py | 84 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/pytec/tecQT.py b/pytec/tecQT.py index 163a4e0..45e5c3e 100644 --- a/pytec/tecQT.py +++ b/pytec/tecQT.py @@ -38,7 +38,7 @@ TECparams = [ [ GUIparams = [[ {'name': 'Disable Output', 'type': 'action', 'tip': 'Disable Output'}, - {'name': 'Constant Current', 'type': 'bool', 'value': False, 'children': [ + {'name': 'Constant Current', 'type': 'group', 'children': [ {'name': 'Set Current', 'type': 'float', 'value': 0, 'step': 0.1, 'siPrefix': True, 'suffix': 'A'}, ]}, {'name': 'Temperature PID', 'type': 'bool', 'value': False, 'children': [ @@ -62,10 +62,10 @@ GUIparams = [[ ] for _ in range(2)] ## If anything changes in the tree, print a message -def change(param, changes): +def change(param, changes, ch): print("tree changes:") for param, change, data in changes: - path = paramList0.childPath(param) + path = paramList[ch].childPath(param) if path is not None: childName = '.'.join(path) else: @@ -75,6 +75,60 @@ def change(param, changes): print(' data: %s'% str(data)) print(' ----------') + if (childName == 'Disable Output'): + tec.set_param('pwm', ch, 'i_set', 0) + paramList[ch].child('Constant Current').child('Set Current').setValue(0) + paramList[ch].child('Temperature PID').setValue(False) + + if (childName == 'Temperature PID'): + if (data): + tec.set_param("pwm", ch, "pid") + else: + tec.set_param('pwm', ch, 'i_set', paramList[ch].child('Constant Current').child('Set Current').value()) + + if (childName == 'Constant Current.Set Current'): + tec.set_param('pwm', ch, 'i_set', data) + paramList[ch].child('Temperature PID').setValue(False) + + if (childName == 'Temperature PID.Set Temperature'): + tec.set_param('pid', ch, 'target', data) + + if (childName == 'Output Config.Max Current'): + tec.set_param('pwm', ch, 'max_i_pos', data) + tec.set_param('pwm', ch, 'max_i_neg', data) + tec.set_param('pid', ch, 'output_min', -data) + tec.set_param('pid', ch, 'output_max', data) + + if (childName == 'Output Config.Max Voltage'): + tec.set_param('pwm', ch, 'max_v', data) + + if (childName == 'Thermistor Config.T0'): + tec.set_param('s-h', ch, 't0', data) + + if (childName == 'Thermistor Config.R0'): + tec.set_param('s-h', ch, 'r0', data) + + if (childName == 'Thermistor Config.Beta'): + tec.set_param('s-h', ch, 'b', data) + + if (childName == 'PID Config.kP'): + tec.set_param('pid', ch, 'kp', data) + + if (childName == 'PID Config.kI'): + tec.set_param('pid', ch, 'ki', data) + + if (childName == 'PID Config.kD'): + tec.set_param('pid', ch, 'kd', data) + + if (childName == 'Save'): + tec.save_config() + +def change0(param, changes): + change(param, changes, 0) + +def change1(param, changes): + change(param, changes, 1) + class Curves: def __init__(self, legend: str, key: str, channel: int, color: str, buffer_len: int, period: int): self.curveItem = pg.PlotCurveItem(pen=({'color': color, 'width': 1})) @@ -136,9 +190,7 @@ def TECsync(): children['value'] = tec.get_pid()[channel]['target'] def refreshTreeParam(tempTree:dict, channel:int) -> dict: - print(tempTree) - print(type(tempTree)) - tempTree['children']['Constant Current']['value'] = not TECparams[channel][0]['children'][0]['value'] + # tempTree['children']['Constant Current']['value'] = not TECparams[channel][0]['children'][0]['value'] tempTree['children']['Constant Current']['children']['Set Current']['value'] = TECparams[channel][1]['children'][3]['value'] tempTree['children']['Temperature PID']['value'] = TECparams[channel][0]['children'][0]['value'] tempTree['children']['Temperature PID']['children']['Set Temperature']['value'] = TECparams[channel][4]['children'][0]['value'] @@ -183,19 +235,21 @@ if __name__ == '__main__': cw.setLayout(l) ## Create tree of Parameter objects - paramList0 = Parameter.create(name='GUIparams', type='group', children=GUIparams[0]) - paramList0.sigTreeStateChanged.connect(change) - ch0Tree = ParameterTree() - ch0Tree.setParameters(paramList0, showTop=False) + paramList = [Parameter.create(name='GUIparams', type='group', children=GUIparams[0]), + Parameter.create(name='GUIparams', type='group', children=GUIparams[1])] - paramList1 = Parameter.create(name='GUIparams', type='group', children=GUIparams[1]) - paramList1.sigTreeStateChanged.connect(change) + paramList[0].sigTreeStateChanged.connect(change0) + print(paramList[0].children()) + ch0Tree = ParameterTree() + ch0Tree.setParameters(paramList[0], showTop=False) + + paramList[1].sigTreeStateChanged.connect(change1) ch1Tree = ParameterTree() - ch1Tree.setParameters(paramList1, showTop=False) + ch1Tree.setParameters(paramList[1], showTop=False) TECsync() - paramList0.restoreState(refreshTreeParam(paramList0.saveState(), 0)) - paramList1.restoreState(refreshTreeParam(paramList1.saveState(), 1)) + paramList[0].restoreState(refreshTreeParam(paramList[0].saveState(), 0)) + paramList[1].restoreState(refreshTreeParam(paramList[1].saveState(), 1)) layout.addWidget(ch0Tree, 1, 1, 1, 1) layout.addWidget(ch1Tree, 2, 1, 1, 1) -- 2.44.2 From 1940367dc86307dd58cabab3f485af2eea857b40 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Mon, 6 Jun 2022 15:25:37 +0800 Subject: [PATCH 13/17] fix whitespace error --- pytec/pytec/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytec/pytec/client.py b/pytec/pytec/client.py index 642e831..4d9eac6 100644 --- a/pytec/pytec/client.py +++ b/pytec/pytec/client.py @@ -23,7 +23,7 @@ class Client: return line def _command(self, *command): - self._socket.sendall((" ".join(command) + "\n").encode('utf-8')) + self._socket.sendall(((" ".join(command)).strip() + "\n").encode('utf-8')) line = self._read_line() response = json.loads(line) -- 2.44.2 From c52cdceec54ed04fe6bd59d3d80e0730a758e5e9 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Mon, 6 Jun 2022 21:28:30 +0800 Subject: [PATCH 14/17] fix docs, fix i_set, fix GUI param ranges --- README.md | 2 +- pytec/tecQT.py | 10 +++++----- src/channels.rs | 7 ++++++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 10d2fc7..6c3f705 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,7 @@ postfilter rate can be tuned with the `postfilter` command. - Connect TEC module device 1 to TEC1- and TEC1+. - The GND pin is for shielding not for sinking TEC module currents. -When using a TEC module with the Thermostat, the Thermostat expects the thermal load (where the thermistor is located) to heat up with a positive software current set point, and cool down with a negative current set point. +When using a TEC module with the Thermostat, the Thermostat expects the thermal load (where the thermistor is located) to cool down with a positive software current set point, and heat up with a negative current set point. Testing heat flow direction with a low set current is recommended before installation of the TEC module. diff --git a/pytec/tecQT.py b/pytec/tecQT.py index 45e5c3e..c267d6a 100644 --- a/pytec/tecQT.py +++ b/pytec/tecQT.py @@ -39,17 +39,17 @@ TECparams = [ [ GUIparams = [[ {'name': 'Disable Output', 'type': 'action', 'tip': 'Disable Output'}, {'name': 'Constant Current', 'type': 'group', 'children': [ - {'name': 'Set Current', 'type': 'float', 'value': 0, 'step': 0.1, 'siPrefix': True, 'suffix': 'A'}, + {'name': 'Set Current', 'type': 'float', 'value': 0, 'step': 0.1, 'limits': (-3, 3), 'siPrefix': True, 'suffix': 'A'}, ]}, {'name': 'Temperature PID', 'type': 'bool', 'value': False, 'children': [ - {'name': 'Set Temperature', 'type': 'float', 'value': 25, 'step': 0.1, 'siPrefix': True, 'suffix': 'C'}, + {'name': 'Set Temperature', 'type': 'float', 'value': 25, 'step': 0.1, 'limits': (-273, 300), 'siPrefix': True, 'suffix': 'C'}, ]}, {'name': 'Output Config', 'expanded': False, 'type': 'group', 'children': [ - {'name': 'Max Current', 'type': 'float', 'value': 0, 'step': 0.1, 'siPrefix': True, 'suffix': 'A'}, - {'name': 'Max Voltage', 'type': 'float', 'value': 0, 'step': 0.1, 'siPrefix': True, 'suffix': 'V'}, + {'name': 'Max Current', 'type': 'float', 'value': 0, 'step': 0.1, 'limits': (0, 3), 'siPrefix': True, 'suffix': 'A'}, + {'name': 'Max Voltage', 'type': 'float', 'value': 0, 'step': 0.1, 'limits': (0, 5), 'siPrefix': True, 'suffix': 'V'}, ]}, {'name': 'Thermistor Config', 'expanded': False, 'type': 'group', 'children': [ - {'name': 'T0', 'type': 'float', 'value': 25, 'step': 0.1, 'siPrefix': True, 'suffix': 'C'}, + {'name': 'T0', 'type': 'float', 'value': 25, 'step': 0.1, 'limits': (-100, 100), 'siPrefix': True, 'suffix': 'C'}, {'name': 'R0', 'type': 'float', 'value': 10000, 'step': 1, 'siPrefix': True, 'suffix': 'Ohm'}, {'name': 'Beta', 'type': 'float', 'value': 3950, 'step': 1}, ]}, diff --git a/src/channels.rs b/src/channels.rs index 7aa34e9..c26a0e2 100644 --- a/src/channels.rs +++ b/src/channels.rs @@ -113,7 +113,12 @@ impl Channels { } pub fn get_i(&mut self, channel: usize) -> ElectricCurrent { - let center_point = self.get_center(channel); + let center_point = match channel.into() { + 0 => self.channel0.vref_meas, + 1 => self.channel1.vref_meas, + _ => unreachable!(), + }; + // let center_point = self.get_center(channel); let r_sense = ElectricalResistance::new::(R_SENSE); let voltage = self.get_dac(channel); let i_tec = (voltage - center_point) / (10.0 * r_sense); -- 2.44.2 From 8d3a7292e377d74446f2b00413d406e7b29865ce Mon Sep 17 00:00:00 2001 From: topquark12 Date: Mon, 6 Jun 2022 23:18:44 +0800 Subject: [PATCH 15/17] WIP: adding autotune --- pytec/tecQT.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/pytec/tecQT.py b/pytec/tecQT.py index c267d6a..8ca7fd9 100644 --- a/pytec/tecQT.py +++ b/pytec/tecQT.py @@ -4,6 +4,8 @@ from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, reg import numpy as np import pyqtgraph as pg from pytec.client import Client +from enum import Enum +from autotune import PIDAutotune, PIDAutotuneState rec_len = 1000 refresh_period = 20 @@ -35,7 +37,6 @@ TECparams = [ [ ]}, ] for _ in range(2)] - GUIparams = [[ {'name': 'Disable Output', 'type': 'action', 'tip': 'Disable Output'}, {'name': 'Constant Current', 'type': 'group', 'children': [ @@ -57,10 +58,18 @@ GUIparams = [[ {'name': 'kP', 'type': 'float', 'value': 0, 'step': 0.1}, {'name': 'kI', 'type': 'float', 'value': 0, 'step': 0.1}, {'name': 'kD', 'type': 'float', 'value': 0, 'step': 0.1}, + {'name': 'PID Auto Tune', 'expanded': False, 'type': 'group', 'children': [ + {'name': 'Target Temperature', 'type': 'float', 'value': 20, 'step': 0.1, 'siPrefix': True, 'suffix': 'C'}, + {'name': 'Test Current', 'type': 'float', 'value': 1, 'step': 0.1, 'siPrefix': True, 'suffix': 'A'}, + {'name': 'Temperature Swing', 'type': 'float', 'value': 1.5, 'step': 0.1, 'siPrefix': True, 'suffix': 'C'}, + {'name': 'Run', 'type': 'action', 'tip': 'Run'}, + ]}, ]}, - {'name': 'Save', 'type': 'action', 'tip': 'Save'}, + {'name': 'Save to flash', 'type': 'action', 'tip': 'Save to flash'}, ] for _ in range(2)] +autoTuneState = [PIDAutotuneState.STATE_OFF, 'idle'] + ## If anything changes in the tree, print a message def change(param, changes, ch): print("tree changes:") @@ -120,7 +129,10 @@ def change(param, changes, ch): if (childName == 'PID Config.kD'): tec.set_param('pid', ch, 'kd', data) - if (childName == 'Save'): + if (childName == 'PID Config.PID Auto Tune.Run'): + autoTuneState[ch] = 'triggered' + + if (childName == 'Save to flash'): tec.save_config() def change0(param, changes): @@ -174,6 +186,7 @@ def TECsync(): for data in tec.report_mode(): for children in parents['children']: children['value'] = data[channel][children['tag']] + print(data[channel][children['tag']]) if quit: break if parents['tag'] == 'pwm': @@ -190,7 +203,6 @@ def TECsync(): children['value'] = tec.get_pid()[channel]['target'] def refreshTreeParam(tempTree:dict, channel:int) -> dict: - # tempTree['children']['Constant Current']['value'] = not TECparams[channel][0]['children'][0]['value'] tempTree['children']['Constant Current']['children']['Set Current']['value'] = TECparams[channel][1]['children'][3]['value'] tempTree['children']['Temperature PID']['value'] = TECparams[channel][0]['children'][0]['value'] tempTree['children']['Temperature PID']['children']['Set Temperature']['value'] = TECparams[channel][4]['children'][0]['value'] @@ -213,6 +225,11 @@ def updateData(): ch1tempGraph.update(data, cnt) ch0currentGraph.update(data, cnt) ch1currentGraph.update(data, cnt) + + for state in autoTuneState: + if state == 'triggered': + state = 'tuning' + if quit: break @@ -238,12 +255,8 @@ if __name__ == '__main__': paramList = [Parameter.create(name='GUIparams', type='group', children=GUIparams[0]), Parameter.create(name='GUIparams', type='group', children=GUIparams[1])] - paramList[0].sigTreeStateChanged.connect(change0) - print(paramList[0].children()) ch0Tree = ParameterTree() - ch0Tree.setParameters(paramList[0], showTop=False) - - paramList[1].sigTreeStateChanged.connect(change1) + ch0Tree.setParameters(paramList[0], showTop=False) ch1Tree = ParameterTree() ch1Tree.setParameters(paramList[1], showTop=False) @@ -251,6 +264,9 @@ if __name__ == '__main__': paramList[0].restoreState(refreshTreeParam(paramList[0].saveState(), 0)) paramList[1].restoreState(refreshTreeParam(paramList[1].saveState(), 1)) + paramList[0].sigTreeStateChanged.connect(change0) + paramList[1].sigTreeStateChanged.connect(change1) + layout.addWidget(ch0Tree, 1, 1, 1, 1) layout.addWidget(ch1Tree, 2, 1, 1, 1) -- 2.44.2 From 3f6419835fcceecd59a309abf43b9b040c01093f Mon Sep 17 00:00:00 2001 From: topquark12 Date: Tue, 7 Jun 2022 13:54:18 +0800 Subject: [PATCH 16/17] add autotune --- pytec/autotune.py | 25 ++++++++++++++++++++++++- pytec/tecQT.py | 38 ++++++++++++++++++++++++++++++-------- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/pytec/autotune.py b/pytec/autotune.py index c1f593e..bf12432 100644 --- a/pytec/autotune.py +++ b/pytec/autotune.py @@ -17,6 +17,7 @@ class PIDAutotuneState(Enum): STATE_RELAY_STEP_DOWN = 'relay step down' STATE_SUCCEEDED = 'succeeded' STATE_FAILED = 'failed' + STATE_READY = 'ready' class PIDAutotune: @@ -56,6 +57,20 @@ class PIDAutotune: self._Ku = 0 self._Pu = 0 + def setParam(self, target, step, noiseband, sampletime, lookback): + self._setpoint = target + self._outputstep = step + self._out_max = step + self._out_min = -step + self._noiseband = noiseband + self._inputs = deque(maxlen=round(lookback / sampletime)) + + def setReady(self): + self._state = PIDAutotuneState.STATE_READY + + def setOff(self): + self._state = PIDAutotuneState.STATE_OFF + def state(self): """Get the current state.""" return self._state @@ -81,6 +96,13 @@ class PIDAutotune: kd = divisors[2] * self._Ku * self._Pu return PIDAutotune.PIDParams(kp, ki, kd) + def get_tec_pid (self): + divisors = self._tuning_rules["tyreus-luyben"] + kp = self._Ku * divisors[0] + ki = divisors[1] * self._Ku / self._Pu + kd = divisors[2] * self._Ku * self._Pu + return kp, ki, kd + def run(self, input_val, time_input): """To autotune a system, this method must be called periodically. @@ -95,7 +117,8 @@ class PIDAutotune: if (self._state == PIDAutotuneState.STATE_OFF or self._state == PIDAutotuneState.STATE_SUCCEEDED - or self._state == PIDAutotuneState.STATE_FAILED): + or self._state == PIDAutotuneState.STATE_FAILED + or self._state == PIDAutotuneState.STATE_READY): self._state = PIDAutotuneState.STATE_RELAY_STEP_UP self._last_run_timestamp = now diff --git a/pytec/tecQT.py b/pytec/tecQT.py index 8ca7fd9..4a94439 100644 --- a/pytec/tecQT.py +++ b/pytec/tecQT.py @@ -68,7 +68,8 @@ GUIparams = [[ {'name': 'Save to flash', 'type': 'action', 'tip': 'Save to flash'}, ] for _ in range(2)] -autoTuneState = [PIDAutotuneState.STATE_OFF, 'idle'] +autoTuner = [PIDAutotune(20, 1, 1, 1.5, refresh_period / 1000), + PIDAutotune(20, 1, 1, 1.5, refresh_period / 1000)] ## If anything changes in the tree, print a message def change(param, changes, ch): @@ -87,7 +88,8 @@ def change(param, changes, ch): if (childName == 'Disable Output'): tec.set_param('pwm', ch, 'i_set', 0) paramList[ch].child('Constant Current').child('Set Current').setValue(0) - paramList[ch].child('Temperature PID').setValue(False) + paramList[ch].child('Temperature PID').setValue(False) + autoTuner[ch].setOff() if (childName == 'Temperature PID'): if (data): @@ -130,7 +132,13 @@ def change(param, changes, ch): tec.set_param('pid', ch, 'kd', data) if (childName == 'PID Config.PID Auto Tune.Run'): - autoTuneState[ch] = 'triggered' + autoTuner[ch].setParam(paramList[ch].child('PID Config').child('PID Auto Tune').child('Target Temperature').value(), + paramList[ch].child('PID Config').child('PID Auto Tune').child('Test Current').value(), + paramList[ch].child('PID Config').child('PID Auto Tune').child('Temperature Swing').value(), + refresh_period / 1000, + 1) + autoTuner[ch].setReady() + paramList[ch].child('Temperature PID').setValue(False) if (childName == 'Save to flash'): tec.save_config() @@ -185,8 +193,8 @@ def TECsync(): if parents['tag'] == 'report': for data in tec.report_mode(): for children in parents['children']: + print(data) children['value'] = data[channel][children['tag']] - print(data[channel][children['tag']]) if quit: break if parents['tag'] == 'pwm': @@ -226,10 +234,24 @@ def updateData(): ch0currentGraph.update(data, cnt) ch1currentGraph.update(data, cnt) - for state in autoTuneState: - if state == 'triggered': - state = 'tuning' - + for channel in range (2): + if (autoTuner[channel].state() == PIDAutotuneState.STATE_READY or + autoTuner[channel].state() == PIDAutotuneState.STATE_RELAY_STEP_UP or + autoTuner[channel].state() == PIDAutotuneState.STATE_RELAY_STEP_DOWN): + autoTuner[channel].run(data[channel]['temperature'], data[channel]['time']) + tec.set_param('pwm', channel, 'i_set', autoTuner[channel].output()) + elif (autoTuner[channel].state() == PIDAutotuneState.STATE_SUCCEEDED): + kp, ki, kd = autoTuner[channel].get_tec_pid() + autoTuner[channel].setOff() + paramList[channel].child('PID Config').child('kP').setValue(kp) + paramList[channel].child('PID Config').child('kI').setValue(ki) + paramList[channel].child('PID Config').child('kD').setValue(kd) + tec.set_param('pid', channel, 'kp', kp) + tec.set_param('pid', channel, 'ki', ki) + tec.set_param('pid', channel, 'kd', kd) + elif (autoTuner[channel].state() == PIDAutotuneState.STATE_FAILED): + tec.set_param('pwm', channel, 'i_set', 0) + autoTuner[channel].setOff() if quit: break -- 2.44.2 From 718ef996092e2897e027bb49e9243a8c3e96121a Mon Sep 17 00:00:00 2001 From: topquark12 Date: Thu, 30 Jun 2022 14:25:56 +0800 Subject: [PATCH 17/17] update docs --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c3f705..7106f06 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,21 @@ On a Windows machine install [st.com](https://st.com) DfuSe USB device firmware openocd -f interface/stlink-v2-1.cfg -f target/stm32f4x.cfg -c "program target/thumbv7em-none-eabihf/release/thermostat verify reset;exit" ``` -## Network +## GUI Usage + +A GUI has been developed for easy configuration and plotting of key parameters. + +The Python GUI program is located at pytec/tecQT.py + +The GUI is developed based on the Python library pyqtgraph. The environment needed to run the GUI is configured automatically by running: + +```shell +nix develop +``` + +The GUI program assumes the default IP and port of 192.168.1.26 23 is used. If a different IP or port is used, the IP and port setting should be changed in the GUI code. + +## Command Line Usage ### Connecting -- 2.44.2