Add paramtree view, without updates

Signed-off-by: Egor Savkin <es@m-labs.hk>
gui-paramtree
Egor Savkin 2023-06-28 15:01:47 +08:00
parent d5fb8b8317
commit b4eb569957
3 changed files with 136 additions and 3 deletions

View File

@ -55,9 +55,45 @@
dontFixup = true; dontFixup = true;
}; };
qasync = pkgs.python3Packages.buildPythonPackage rec {
pname = "qasync";
version = "0.24.0";
src = pkgs.fetchFromGitHub {
owner = "CabbageDevelopment";
repo = "qasync";
rev = "v${version}";
sha256 = "sha256-ls5F+VntXXa3n+dULaYWK9sAmwly1nk/5+RGWLrcf2Y=";
};
propagatedBuildInputs = [ pkgs.python3Packages.pyqt6 ];
checkInputs = [ pkgs.python3Packages.pytest ];
checkPhase = ''
pytest -k 'test_qthreadexec.py' # the others cause the test execution to be aborted, I think because of asyncio
'';
};
thermostat_gui = pkgs.python3Packages.buildPythonPackage rec {
pname = "thermostat_gui";
version = "0.0.0";
src = self;
preBuild =
''
export VERSIONEER_OVERRIDE=${version}
export VERSIONEER_REV=v0.0.0
'';
nativeBuildInputs = [ pkgs.qt6.wrapQtAppsHook ];
propagatedBuildInputs = (with pkgs.python3Packages; [ pyqtgraph pyqt6 qasync]);
dontWrapQtApps = true;
postFixup = ''
ls -al $out/
wrapQtApp "$out/pytec/tec_qt"
'';
};
in { in {
packages.x86_64-linux = { packages.x86_64-linux = {
inherit thermostat; inherit thermostat qasync thermostat_gui;
}; };
hydraJobs = { hydraJobs = {
@ -71,7 +107,7 @@
rustPlatform.rust.cargo rustPlatform.rust.cargo
openocd dfu-util openocd dfu-util
] ++ (with python3Packages; [ ] ++ (with python3Packages; [
numpy matplotlib pyqtgraph setuptools pyqt6 numpy matplotlib pyqtgraph setuptools pyqt6 qasync
]); ]);
shellHook= shellHook=
'' ''

View File

@ -40,6 +40,7 @@ class Client:
line = self._read_line() line = self._read_line()
response = json.loads(line) response = json.loads(line)
logging.debug(f"{command}: {response}")
if "error" in response: if "error" in response:
raise CommandError(response["error"]) raise CommandError(response["error"])
return response return response

View File

@ -6,6 +6,10 @@ import pyqtgraph as pg
import sys import sys
import argparse import argparse
import logging import logging
import asyncio
import atexit
from qasync import asyncSlot, QEventLoop
import qasync
from pytec.client import Client from pytec.client import Client
# pyuic6 -x tec_qt.ui -o ui_tec_qt.py # pyuic6 -x tec_qt.ui -o ui_tec_qt.py
@ -22,6 +26,52 @@ client_watcher = None
app: QtWidgets.QApplication = None app: QtWidgets.QApplication = None
class CommandsParameter(Parameter):
def __init__(self, **opts):
super().__init__()
self.opts["commands"] = opts.get("commands", None)
self.opts["payload"] = opts.get("payload", None)
ThermostatParams = [[
{'name': 'Constant Current', 'type': 'float', 'value': 0, 'step': 0.1, 'limits': (-3, 3), 'siPrefix': True,
'suffix': 'A', 'commands': [f'pwm {ch} i_set {{value}}']},
{'name': 'Temperature PID', 'type': 'bool', 'value': False, 'commands': [f'pwm {ch} pid'], 'payload': ch,
'children': [
{'name': 'Set Temperature', 'type': 'float', 'value': 25, 'step': 0.1, 'limits': (-273, 300), 'siPrefix': True,
'suffix': '°C', 'commands': [f'pid {ch} target {{value}}']},
]},
{'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', 'commands': [f'pwm {ch} max_i_pos {{value}}', f'pwm {ch} max_i_neg {{value}}',
f'pid {ch} output_min -{{value}}', f'pid {ch} output_max {{value}}']},
{'name': 'Max Voltage', 'type': 'float', 'value': 0, 'step': 0.1, 'limits': (0, 5), 'siPrefix': True,
'suffix': 'V', 'commands': [f'pwm {ch} max_v {{value}}']},
]},
{'name': 'Thermistor Config', 'expanded': False, 'type': 'group', 'children': [
{'name': 'T0', 'type': 'float', 'value': 25, 'step': 0.1, 'limits': (-100, 100), 'siPrefix': True,
'suffix': 'C', 'commands': [f's-h {ch} t0 {{value}}']},
{'name': 'R0', 'type': 'float', 'value': 10000, 'step': 1, 'siPrefix': True, 'suffix': 'Ohm',
'commands': [f's-h {ch} r0 {{value}}']},
{'name': 'Beta', 'type': 'float', 'value': 3950, 'step': 1, 'commands': [f's-h {ch} b {{value}}']},
]},
{'name': 'PID Config', 'expanded': False, 'type': 'group', 'children': [
{'name': 'kP', 'type': 'float', 'value': 0, 'step': 0.1, 'commands': [f'pid {ch} kp {{value}}']},
{'name': 'kI', 'type': 'float', 'value': 0, 'step': 0.1, 'commands': [f'pid {ch} ki {{value}}']},
{'name': 'kD', 'type': 'float', 'value': 0, 'step': 0.1, 'commands': [f'pid {ch} kd {{value}}']},
{'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'},
]},
]}
] for ch in range(2)]
params = [CommandsParameter.create(name='Thermostat Params 0', type='group', children=ThermostatParams[0]),
CommandsParameter.create(name='Thermostat Params 1', type='group', children=ThermostatParams[1])]
def get_argparser(): def get_argparser():
parser = argparse.ArgumentParser(description="ARTIQ master") parser = argparse.ArgumentParser(description="ARTIQ master")
@ -58,7 +108,7 @@ class WatchConnectTask(QThread):
tec_client = Client(host=self.ip, port=self.port, timeout=30) tec_client = Client(host=self.ip, port=self.port, timeout=30)
self.connected.emit(True) self.connected.emit(True)
thread_pool.start(ClientTask(lambda: self.hw_rev.emit(tec_client.hw_rev()))) thread_pool.start(ClientTask(lambda: self.hw_rev.emit(tec_client.hw_rev())))
#thread_pool.start(ClientTask(lambda: self.fan_update.emit(tec_client.fan()))) thread_pool.start(ClientTask(lambda: self.fan_update.emit(tec_client.fan())))
except Exception as e: except Exception as e:
logging.error(f"Failed communicating to the {self.ip}:{self.port}: {e}") logging.error(f"Failed communicating to the {self.ip}:{self.port}: {e}")
self.connected.emit(False) self.connected.emit(False)
@ -90,6 +140,9 @@ class ClientWatcher(QThread):
def update_params(self): def update_params(self):
self.fan_update.emit(tec_client.fan()) self.fan_update.emit(tec_client.fan())
self.pwm_update.emit(tec_client.get_pwm())
self.report_update.emit(tec_client._command("report"))
self.pid_update.emit(tec_client.get_pid())
@pyqtSlot() @pyqtSlot()
def stop_watching(self): def stop_watching(self):
@ -198,6 +251,42 @@ def connect():
app.aboutToQuit.connect(connection_watcher.terminate) app.aboutToQuit.connect(connection_watcher.terminate)
def update_pid(pid_settings):
for settings in pid_settings:
channel = settings["channel"]
with QSignalBlocker(params[channel].sigTreeStateChanged) as _:
params[channel].child("PID Config", "kP").setValue(settings["parameters"]["kp"])
params[channel].child("PID Config", "kI").setValue(settings["parameters"]["ki"])
params[channel].child("PID Config", "kD").setValue(settings["parameters"]["kd"])
if params[channel].child("Temperature PID").value():
params[channel].child("Temperature PID", "Set Temperature").setValue(settings["target"])
def update_report(report_data):
for settings in report_data:
channel = settings["channel"]
with QSignalBlocker(params[channel].sigTreeStateChanged) as _:
params[channel].child("Temperature PID").setValue(settings["pid_engaged"])
def send_command(param, changes):
for param, change, data in changes:
if param.name() == 'Temperature PID' and not data:
ch = param.opts["payload"]
thread_pool.start(ClientTask(
lambda: tec_client.set_param('pwm', ch, 'i_set', params[ch].child('Constant Current').value())))
elif param.opts.get("commands", None) is not None:
thread_pool.start(ClientTask(lambda: [tec_client._command(x.format(value=data)) for x in param.opts["commands"]]))
def set_param_tree():
ui.ch0_tree.setParameters(params[0], showTop=False)
ui.ch1_tree.setParameters(params[1], showTop=False)
params[0].sigTreeStateChanged.connect(send_command)
params[1].sigTreeStateChanged.connect(send_command)
def main(): def main():
global ui, thread_pool, app global ui, thread_pool, app
args = get_argparser().parse_args() args = get_argparser().parse_args()
@ -205,6 +294,11 @@ def main():
logging.basicConfig(level=getattr(logging, args.logLevel)) logging.basicConfig(level=getattr(logging, args.logLevel))
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
loop = QEventLoop(app)
asyncio.set_event_loop(loop)
atexit.register(loop.close)
main_window = QtWidgets.QMainWindow() main_window = QtWidgets.QMainWindow()
ui = Ui_MainWindow() ui = Ui_MainWindow()
ui.setupUi(main_window) ui.setupUi(main_window)
@ -217,6 +311,8 @@ def main():
ui.fan_power_slider.valueChanged.connect(fan_set) ui.fan_power_slider.valueChanged.connect(fan_set)
ui.fan_auto_box.stateChanged.connect(fan_auto_set) ui.fan_auto_box.stateChanged.connect(fan_auto_set)
set_param_tree()
if args.connect: if args.connect:
if args.IP: if args.IP:
ui.ip_set_line.setText(args.IP) ui.ip_set_line.setText(args.IP)