Compare commits

...

10 Commits

Author SHA1 Message Date
bbc9204b7f ctrl_panel: Use ADC sample interval as PID unit
The Thermostat PID gains are actually in units relative to the sampling
interval of the Thermostat, and not SI seconds. Reflect that accordingly
in the units of PIDs.

See https://hackmd.io/IACbwcOTSt6Adj3_F9bKuw#Units for more details.
2024-10-14 17:35:49 +08:00
d092c6f9a8 Remove ["value"] in pwm values 2024-10-14 16:53:30 +08:00
7f1223b1b9 ctrl_panel: Add polarity option in output settings 2024-10-14 16:53:02 +08:00
b1e5a843eb QVariantList -> list 2024-10-07 17:48:24 +08:00
2b02ea334a No need to create a new task for waiting 2024-10-07 17:48:24 +08:00
3b2743c079 super init's first 2024-10-07 17:48:24 +08:00
a7ce942aea Format 2024-10-07 17:48:24 +08:00
ea8469a690 ch_tuning -> autotuning_channels 2024-10-07 17:48:24 +08:00
2467fc2ee2 Module docstring?? 2024-10-07 17:48:24 +08:00
a3baadd490 Reorder imports 2024-10-07 17:48:24 +08:00
6 changed files with 58 additions and 40 deletions

View File

@ -44,7 +44,7 @@ class AsyncioClient:
pwm_report = await self.get_pwm()
for pwm_channel in pwm_report:
for limit in ["max_i_neg", "max_i_pos", "max_v"]:
if pwm_channel[limit]["value"] == 0.0:
if pwm_channel[limit] == 0.0:
logging.warning(
"`{}` limit is set to zero on channel {}".format(
limit, pwm_channel["channel"]

View File

@ -28,13 +28,13 @@ class Thermostat(QObject, metaclass=PropertyMeta):
NUM_CHANNELS = 2
def __init__(self, parent, update_s, disconnect_cb=None):
super().__init__(parent)
self._update_s = update_s
self._client = AsyncioClient()
self._watch_task = None
self._update_params_task = None
self.disconnect_cb = disconnect_cb
super().__init__(parent)
self.connection_state = ThermostatConnectionState.DISCONNECTED
async def start_session(self, host, port):

View File

@ -137,7 +137,7 @@ class CtrlPanel(QObject):
auto_tuner_param = inner_param.opts["pid_autotune"]
self.autotuners.set_params(auto_tuner_param, ch, new_value)
@pyqtSlot("QVariantList")
@pyqtSlot(list)
def update_pid(self, pid_settings):
for settings in pid_settings:
channel = settings["channel"]
@ -161,7 +161,7 @@ class CtrlPanel(QObject):
"output", "control_method", "target"
).set_value_with_lock(settings["target"])
@pyqtSlot("QVariantList")
@pyqtSlot(list)
def update_report(self, report_data):
for settings in report_data:
channel = settings["channel"]
@ -183,7 +183,7 @@ class CtrlPanel(QObject):
"readings", "tec_i"
).set_value_with_lock(settings["tec_i"])
@pyqtSlot("QVariantList")
@pyqtSlot(list)
def update_thermistor(self, sh_data):
for sh_param in sh_data:
channel = sh_param["channel"]
@ -198,22 +198,25 @@ class CtrlPanel(QObject):
sh_param["params"]["b"]
)
@pyqtSlot("QVariantList")
@pyqtSlot(list)
def update_pwm(self, pwm_data):
for pwm_params in pwm_data:
channel = pwm_params["channel"]
with QSignalBlocker(self.params[channel]):
self.params[channel].child(
"output", "polarity"
).set_value_with_lock(pwm_params["polarity"])
self.params[channel].child(
"output", "limits", "max_v"
).set_value_with_lock(pwm_params["max_v"]["value"])
).set_value_with_lock(pwm_params["max_v"])
self.params[channel].child(
"output", "limits", "max_i_pos"
).set_value_with_lock(pwm_params["max_i_pos"]["value"])
).set_value_with_lock(pwm_params["max_i_pos"])
self.params[channel].child(
"output", "limits", "max_i_neg"
).set_value_with_lock(pwm_params["max_i_neg"]["value"])
).set_value_with_lock(pwm_params["max_i_neg"])
@pyqtSlot("QVariantList")
@pyqtSlot(list)
def update_postfilter(self, postfilter_data):
for postfilter_params in postfilter_data:
channel = postfilter_params["channel"]

View File

@ -34,6 +34,19 @@
"type": "group",
"tip": "Settings of the output to the TEC",
"children": [
{
"name": "polarity",
"title": "Polarity",
"type": "list",
"limits": {
"Normal": "normal",
"Reversed": "reversed"
},
"thermostat:set_param": {
"topic": "pwm",
"field": "polarity"
}
},
{
"name": "control_method",
"title": "Control Method",
@ -290,7 +303,7 @@
"title": "Ki",
"type": "float",
"step": 0.1,
"suffix": "Hz",
"suffix": "τ⁻¹",
"noUnitEditing": true,
"compactHeight": false,
"thermostat:set_param": {
@ -305,7 +318,7 @@
"title": "Kd",
"type": "float",
"step": 0.1,
"suffix": "s",
"suffix": "τ",
"noUnitEditing": true,
"compactHeight": false,
"thermostat:set_param": {
@ -456,4 +469,4 @@
"tip": "Load settings from thermostat"
}
]
}
}

View File

@ -10,14 +10,14 @@ class ZeroLimitsWarningView(QObject):
self._lbl = limit_warning
self._style = style
@pyqtSlot("QVariantList")
@pyqtSlot(list)
def set_limits_warning(self, pwm_data: list):
channels_zeroed_limits = [set() for i in range(self._thermostat.NUM_CHANNELS)]
for pwm_params in pwm_data:
channel = pwm_params["channel"]
for limit in "max_i_pos", "max_i_neg", "max_v":
if pwm_params[limit]["value"] == 0.0:
if pwm_params[limit] == 0.0:
channels_zeroed_limits[channel].add(limit)
channel_disabled = [False, False]

View File

@ -1,3 +1,17 @@
"""GUI for the Sinara 8451 Thermostat"""
import json
import asyncio
import logging
import argparse
import importlib.resources
import qasync
from qasync import asyncSlot, asyncClose
from autotune import PIDAutotuneState
from PyQt6 import QtWidgets, QtGui, uic
from PyQt6.QtCore import pyqtSlot
from pytec.gui.model.thermostat import Thermostat, ThermostatConnectionState
from pytec.gui.model.pid_autotuner import PIDAutoTuner
from pytec.gui.view.zero_limits_warning_view import ZeroLimitsWarningView
from pytec.gui.view.thermostat_settings_menu import ThermostatSettingsMenu
from pytec.gui.view.connection_details_menu import ConnectionDetailsMenu
@ -5,18 +19,6 @@ from pytec.gui.view.plot_options_menu import PlotOptionsMenu
from pytec.gui.view.live_plot_view import LiveDataPlotter
from pytec.gui.view.ctrl_panel import CtrlPanel
from pytec.gui.view.info_box import InfoBox
from pytec.gui.model.pid_autotuner import PIDAutoTuner
from pytec.gui.model.thermostat import Thermostat, ThermostatConnectionState
import json
from autotune import PIDAutotuneState
from qasync import asyncSlot, asyncClose
import qasync
import asyncio
import logging
import argparse
from PyQt6 import QtWidgets, QtGui, uic
from PyQt6.QtCore import pyqtSlot
import importlib.resources
def get_argparser():
@ -75,6 +77,7 @@ class MainWindow(QtWidgets.QMainWindow):
for ch in range(self.NUM_CHANNELS):
if self._autotuners.get_state(ch) != PIDAutotuneState.STATE_OFF:
await self._autotuners.stop_pid_from_running(ch)
self._thermostat.disconnect_cb = autotune_disconnect
@pyqtSlot()
@ -82,6 +85,7 @@ class MainWindow(QtWidgets.QMainWindow):
self._info_box.display_info_box(
"Connection Error", "Thermostat connection lost. Is it unplugged?"
)
self._thermostat.connection_error.connect(handle_connection_error)
# Control Panel
@ -103,7 +107,7 @@ class MainWindow(QtWidgets.QMainWindow):
[
[getattr(self, f"ch{ch}_t_graph"), getattr(self, f"ch{ch}_i_graph")]
for ch in range(self.NUM_CHANNELS)
]
],
)
# Bottom bar menus
@ -165,22 +169,22 @@ class MainWindow(QtWidgets.QMainWindow):
@pyqtSlot(int, PIDAutotuneState)
def _on_pid_autotune_state_changed(self, _ch, _state):
ch_tuning = []
autotuning_channels = []
for ch in range(self.NUM_CHANNELS):
if self._autotuners.get_state(ch) in {
PIDAutotuneState.STATE_READY,
PIDAutotuneState.STATE_RELAY_STEP_UP,
PIDAutotuneState.STATE_RELAY_STEP_DOWN,
}:
ch_tuning.append(ch)
autotuning_channels.append(ch)
if len(ch_tuning) == 0:
if len(autotuning_channels) == 0:
self.background_task_lbl.setText("Ready.")
self.loading_spinner.hide()
self.loading_spinner.stop()
else:
self.background_task_lbl.setText(
"Autotuning channel {ch}...".format(ch=ch_tuning)
f"Autotuning channel {autotuning_channels}..."
)
self.loading_spinner.start()
self.loading_spinner.show()
@ -189,14 +193,12 @@ class MainWindow(QtWidgets.QMainWindow):
async def on_connect_btn_clicked(self):
match self._thermostat.connection_state:
case ThermostatConnectionState.DISCONNECTED:
self._connecting_task = asyncio.create_task(
self._thermostat.start_session(
host=self.connection_details_menu.host_set_line.text(),
port=self.connection_details_menu.port_set_spin.value(),
)
)
self._connecting_task = asyncio.current_task()
self._thermostat.connection_state = ThermostatConnectionState.CONNECTING
await self._connecting_task
await self._thermostat.start_session(
host=self.connection_details_menu.host_set_line.text(),
port=self.connection_details_menu.port_set_spin.value(),
)
self._connecting_task = None
self._thermostat.connection_state = ThermostatConnectionState.CONNECTED
self._thermostat.start_watching()