thermostat/pytec/tecQT.py

336 lines
14 KiB
Python
Raw Permalink Normal View History

2022-06-01 17:47:31 +08:00
from pyqtgraph.Qt import QtGui, QtCore
2022-06-05 18:59:05 +08:00
import pyqtgraph.parametertree.parameterTypes as pTypes
from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType
2022-06-01 17:47:31 +08:00
import numpy as np
import pyqtgraph as pg
from pytec.client import Client
2022-06-06 23:18:44 +08:00
from enum import Enum
from autotune import PIDAutotune, PIDAutotuneState
2022-06-01 17:47:31 +08:00
rec_len = 1000
refresh_period = 20
TECparams = [[
2022-06-06 12:38:44 +08:00
{'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)]
GUIparams = [[
2022-06-06 13:49:58 +08:00
{'name': 'Disable Output', 'type': 'action', 'tip': 'Disable Output'},
2022-06-06 15:24:58 +08:00
{'name': 'Constant Current', 'type': 'group', 'children': [
{'name': 'Set Current', 'type': 'float', 'value': 0, 'step': 0.1, 'limits': (-3, 3), 'siPrefix': True,
'suffix': 'A'},
]},
2022-06-06 13:49:58 +08:00
{'name': 'Temperature PID', 'type': 'bool', 'value': False, 'children': [
{'name': 'Set Temperature', 'type': 'float', 'value': 25, 'step': 0.1, 'limits': (-273, 300), 'siPrefix': True,
'suffix': 'C'},
]},
2022-06-06 13:49:58 +08:00
{'name': 'Output Config', 'expanded': False, 'type': 'group', 'children': [
{'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'},
2022-06-05 18:59:05 +08:00
]},
{'name': 'Thermistor Config', 'expanded': False, 'type': 'group', 'children': [
{'name': 'T0', 'type': 'float', 'value': 25, 'step': 0.1, 'limits': (-100, 100), 'siPrefix': True,
'suffix': 'C'},
2022-06-05 18:59:05 +08:00
{'name': 'R0', 'type': 'float', 'value': 10000, 'step': 1, 'siPrefix': True, 'suffix': 'Ohm'},
{'name': 'Beta', 'type': 'float', 'value': 3950, 'step': 1},
]},
{'name': 'PID Config', 'expanded': False, 'type': 'group', 'children': [
2022-06-05 18:59:05 +08:00
{'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': [
2022-06-06 23:18:44 +08:00
{'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'},
]},
2022-06-05 18:59:05 +08:00
]},
2022-06-06 23:18:44 +08:00
{'name': 'Save to flash', 'type': 'action', 'tip': 'Save to flash'},
2022-06-06 13:49:58 +08:00
] for _ in range(2)]
2022-06-05 18:59:05 +08:00
2022-06-07 13:54:18 +08:00
autoTuner = [PIDAutotune(20, 1, 1, 1.5, refresh_period / 1000),
PIDAutotune(20, 1, 1, 1.5, refresh_period / 1000)]
2022-06-06 23:18:44 +08:00
# If anything changes in the tree, print a message
2022-06-06 15:24:58 +08:00
def change(param, changes, ch):
2022-06-05 18:59:05 +08:00
print("tree changes:")
for param, change, data in changes:
2022-06-06 15:24:58 +08:00
path = paramList[ch].childPath(param)
2022-06-05 18:59:05 +08:00
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))
2022-06-05 18:59:05 +08:00
print(' ----------')
if childName == 'Disable Output':
2022-06-06 15:24:58 +08:00
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)
autoTuner[ch].setOff()
2022-06-06 15:24:58 +08:00
if childName == 'Temperature PID':
2022-06-06 15:24:58 +08:00
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':
2022-06-06 15:24:58 +08:00
tec.set_param('pwm', ch, 'i_set', data)
paramList[ch].child('Temperature PID').setValue(False)
2022-06-06 15:24:58 +08:00
if childName == 'Temperature PID.Set Temperature':
2022-06-06 15:24:58 +08:00
tec.set_param('pid', ch, 'target', data)
if childName == 'Output Config.Max Current':
2022-06-06 15:24:58 +08:00
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':
2022-06-06 15:24:58 +08:00
tec.set_param('pwm', ch, 'max_v', data)
if childName == 'Thermistor Config.T0':
2022-06-06 15:24:58 +08:00
tec.set_param('s-h', ch, 't0', data)
if childName == 'Thermistor Config.R0':
2022-06-06 15:24:58 +08:00
tec.set_param('s-h', ch, 'r0', data)
if childName == 'Thermistor Config.Beta':
2022-06-06 15:24:58 +08:00
tec.set_param('s-h', ch, 'b', data)
if childName == 'PID Config.kP':
2022-06-06 15:24:58 +08:00
tec.set_param('pid', ch, 'kp', data)
if childName == 'PID Config.kI':
2022-06-06 15:24:58 +08:00
tec.set_param('pid', ch, 'ki', data)
if childName == 'PID Config.kD':
2022-06-06 15:24:58 +08:00
tec.set_param('pid', ch, 'kd', data)
if childName == 'PID Config.PID Auto Tune.Run':
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)
2022-06-07 13:54:18 +08:00
autoTuner[ch].setReady()
paramList[ch].child('Temperature PID').setValue(False)
2022-06-06 23:18:44 +08:00
if childName == 'Save to flash':
2022-06-06 15:24:58 +08:00
tec.save_config()
2022-06-06 15:24:58 +08:00
def change0(param, changes):
change(param, changes, 0)
2022-06-06 15:24:58 +08:00
def change1(param, changes):
change(param, changes, 1)
2022-06-05 16:03:33 +08:00
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
2022-06-05 16:03:33 +08:00
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:
2022-06-05 16:03:33 +08:00
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)
2022-06-05 16:03:33 +08:00
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))
2022-06-05 16:03:33 +08:00
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])
2022-06-01 17:47:31 +08:00
2022-06-06 12:38:44 +08:00
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']:
2022-06-07 13:54:18 +08:00
print(data)
2022-06-06 12:38:44 +08:00
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']
def refreshTreeParam(tempTree: dict, channel: int) -> dict:
tempTree['children']['Constant Current']['children']['Set Current']['value'] = TECparams[channel][1]['children'][3][
'value']
2022-06-06 13:49:58 +08:00
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
2022-06-06 13:49:58 +08:00
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']
2022-06-06 13:49:58 +08:00
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
2022-06-01 17:47:31 +08:00
2022-06-01 17:47:31 +08:00
cnt = 0
2022-06-05 16:03:33 +08:00
def updateData():
global cnt
2022-06-01 17:47:31 +08:00
for data in tec.report_mode():
2022-06-05 16:03:33 +08:00
ch0tempGraph.update(data, cnt)
ch1tempGraph.update(data, cnt)
ch0currentGraph.update(data, cnt)
ch1currentGraph.update(data, cnt)
2022-06-06 23:18:44 +08:00
for channel in range(2):
2022-06-07 13:54:18 +08:00
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):
2022-06-07 13:54:18 +08:00
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:
2022-06-07 13:54:18 +08:00
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:
2022-06-07 13:54:18 +08:00
tec.set_param('pwm', channel, 'i_set', 0)
autoTuner[channel].setOff()
2022-06-01 17:47:31 +08:00
if quit:
break
cnt += 1
2022-06-06 13:49:58 +08:00
2022-06-01 17:47:31 +08:00
if __name__ == '__main__':
2022-06-06 12:38:44 +08:00
tec = Client(host="192.168.1.26", port=23, timeout=None)
app = pg.mkQApp()
pg.setConfigOptions(antialias=True)
mw = QtGui.QMainWindow()
mw.setWindowTitle('Thermostat Control Panel')
mw.resize(1920, 1200)
2022-06-06 12:38:44 +08:00
cw = QtGui.QWidget()
mw.setCentralWidget(cw)
l = QtGui.QVBoxLayout()
layout = pg.LayoutWidget()
l.addWidget(layout)
cw.setLayout(l)
## Create tree of Parameter objects
2022-06-06 15:24:58 +08:00
paramList = [Parameter.create(name='GUIparams', type='group', children=GUIparams[0]),
Parameter.create(name='GUIparams', type='group', children=GUIparams[1])]
2022-06-06 12:38:44 +08:00
ch0Tree = ParameterTree()
ch0Tree.setParameters(paramList[0], showTop=False)
2022-06-06 12:38:44 +08:00
ch1Tree = ParameterTree()
2022-06-06 15:24:58 +08:00
ch1Tree.setParameters(paramList[1], showTop=False)
2022-06-06 12:38:44 +08:00
2022-06-06 13:49:58 +08:00
TECsync()
2022-06-06 15:24:58 +08:00
paramList[0].restoreState(refreshTreeParam(paramList[0].saveState(), 0))
paramList[1].restoreState(refreshTreeParam(paramList[1].saveState(), 1))
2022-06-06 13:49:58 +08:00
2022-06-06 23:18:44 +08:00
paramList[0].sigTreeStateChanged.connect(change0)
paramList[1].sigTreeStateChanged.connect(change1)
2022-06-06 12:38:44 +08:00
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)])
2022-06-06 12:38:44 +08:00
t = QtCore.QTimer()
t.timeout.connect(updateData)
t.start(refresh_period)
mw.show()
pg.exec()