forked from M-Labs/thermostat
Compare commits
16 Commits
dc5460f591
...
053601862b
Author | SHA1 | Date | |
---|---|---|---|
053601862b | |||
e30d07d707 | |||
326c22d6b2 | |||
b3f629fb4a | |||
c2dccb80f8 | |||
db127f788b | |||
863106d835 | |||
ff4aa61b1e | |||
209ea365c2 | |||
35a179f7fa | |||
94eb6b09fd | |||
2ec059d402 | |||
ac9ddc92a6 | |||
94eb331c96 | |||
40abceb688 | |||
7b662374bc |
@ -30,6 +30,19 @@ class PIDAutoTuner(QObject):
|
||||
def get_state(self, ch):
|
||||
return self.autotuners[ch].state()
|
||||
|
||||
@asyncSlot()
|
||||
async def pid_auto_tune_request(self, ch):
|
||||
match self.get_state(ch):
|
||||
case PIDAutotuneState.STATE_OFF | PIDAutotuneState.STATE_FAILED:
|
||||
self.load_params_and_set_ready(ch)
|
||||
|
||||
case (
|
||||
PIDAutotuneState.STATE_READY
|
||||
| PIDAutotuneState.STATE_RELAY_STEP_UP
|
||||
| PIDAutotuneState.STATE_RELAY_STEP_DOWN
|
||||
):
|
||||
await self.stop_pid_from_running(ch)
|
||||
|
||||
def load_params_and_set_ready(self, ch):
|
||||
self.autotuners[ch].setParam(
|
||||
self.target_temp[ch],
|
||||
|
@ -39,7 +39,6 @@ class Thermostat(QObject, metaclass=PropertyMeta):
|
||||
async def start_session(self, host, port):
|
||||
self.connection_state_changed.emit(ThermostatConnectionState.CONNECTING)
|
||||
await self._client.connect(host, port)
|
||||
await self.get_hw_rev()
|
||||
self.connection_state_changed.emit(ThermostatConnectionState.CONNECTED)
|
||||
self.start_watching()
|
||||
|
||||
@ -55,14 +54,11 @@ class Thermostat(QObject, metaclass=PropertyMeta):
|
||||
exc_info=True,
|
||||
)
|
||||
self.connection_error.emit()
|
||||
self.connection_errored = True
|
||||
return
|
||||
self._update_params_task = asyncio.create_task(self.update_params())
|
||||
await asyncio.sleep(self._update_s)
|
||||
|
||||
@pyqtSlot()
|
||||
def timed_out(self):
|
||||
self.connection_errored = True
|
||||
|
||||
async def get_hw_rev(self):
|
||||
self.hw_rev = await self._client.hw_rev()
|
||||
return self.hw_rev
|
||||
|
@ -1,9 +1,17 @@
|
||||
from PyQt6 import QtWidgets, QtCore
|
||||
from PyQt6.QtCore import pyqtSlot
|
||||
from pytec.gui.model.thermostat import ThermostatConnectionState
|
||||
|
||||
|
||||
class ConnMenu(QtWidgets.QMenu):
|
||||
def __init__(self):
|
||||
def __init__(self, thermostat, connect_btn):
|
||||
super().__init__()
|
||||
self._thermostat = thermostat
|
||||
self._connect_btn = connect_btn
|
||||
self._thermostat.connection_state_changed.connect(
|
||||
self.thermostat_state_change_handler
|
||||
)
|
||||
|
||||
self.setTitle("Connection Settings")
|
||||
|
||||
self.host_set_line = QtWidgets.QLineEdit()
|
||||
@ -13,7 +21,7 @@ class ConnMenu(QtWidgets.QMenu):
|
||||
self.host_set_line.setClearButtonEnabled(True)
|
||||
|
||||
def connect_on_enter_press():
|
||||
self.connect_btn.click()
|
||||
self._connect_btn.click()
|
||||
self.hide()
|
||||
|
||||
self.host_set_line.returnPressed.connect(connect_on_enter_press)
|
||||
@ -54,3 +62,12 @@ class ConnMenu(QtWidgets.QMenu):
|
||||
exit_action.setDefaultWidget(self.exit_button)
|
||||
self.addAction(exit_action)
|
||||
self.exit_action = exit_action
|
||||
|
||||
@pyqtSlot(ThermostatConnectionState)
|
||||
def thermostat_state_change_handler(self, state):
|
||||
self.host_set_line.setEnabled(
|
||||
state == ThermostatConnectionState.DISCONNECTED
|
||||
)
|
||||
self.port_set_spin.setEnabled(
|
||||
state == ThermostatConnectionState.DISCONNECTED
|
||||
)
|
||||
|
@ -94,13 +94,14 @@ class CtrlPanel(QObject):
|
||||
)
|
||||
self.params[i].child(
|
||||
"PID Config", "PID Auto Tune", "Run"
|
||||
).sigActivated.connect(partial(self.pid_auto_tune_request, i))
|
||||
).sigActivated.connect(partial(self.autotuners.pid_auto_tune_request, i))
|
||||
|
||||
self.thermostat.pid_update.connect(self.update_pid)
|
||||
self.thermostat.report_update.connect(self.update_report)
|
||||
self.thermostat.thermistor_update.connect(self.update_thermistor)
|
||||
self.thermostat.pwm_update.connect(self.update_pwm)
|
||||
self.thermostat.postfilter_update.connect(self.update_postfilter)
|
||||
self.autotuners.autotune_state_changed.connect(self.update_pid_autotune)
|
||||
|
||||
def _setValue(self, value, blockSignal=None):
|
||||
"""
|
||||
@ -259,6 +260,31 @@ class CtrlPanel(QObject):
|
||||
"Thermistor Config", "Postfilter Rate"
|
||||
).setValue(postfilter_params["rate"])
|
||||
|
||||
def update_pid_autotune(self, ch, state):
|
||||
match state:
|
||||
case PIDAutotuneState.STATE_OFF:
|
||||
self.change_params_title(
|
||||
ch, ("PID Config", "PID Auto Tune", "Run"), "Run"
|
||||
)
|
||||
case (
|
||||
PIDAutotuneState.STATE_READY
|
||||
| PIDAutotuneState.STATE_RELAY_STEP_UP
|
||||
| PIDAutotuneState.STATE_RELAY_STEP_DOWN
|
||||
):
|
||||
self.change_params_title(
|
||||
ch, ("PID Config", "PID Auto Tune", "Run"), "Stop"
|
||||
)
|
||||
case PIDAutotuneState.STATE_SUCCEEDED:
|
||||
self.info_box.display_info_box(
|
||||
"PID Autotune Success",
|
||||
f"Channel {ch} PID Config has been loaded to Thermostat. Regulating temperature.",
|
||||
)
|
||||
case PIDAutotuneState.STATE_FAILED:
|
||||
self.info_box.display_info_box(
|
||||
"PID Autotune Failed",
|
||||
f"Channel {ch} PID Autotune has failed.",
|
||||
)
|
||||
|
||||
@asyncSlot(int)
|
||||
async def load_settings(self, ch):
|
||||
await self.thermostat.load_cfg(ch)
|
||||
@ -277,17 +303,3 @@ class CtrlPanel(QObject):
|
||||
f"Channel {ch} settings has been saved to flash.\n"
|
||||
"It will be loaded on Thermostat reset, or when settings are explicitly loaded.",
|
||||
)
|
||||
|
||||
@asyncSlot()
|
||||
async def pid_auto_tune_request(self, ch=0):
|
||||
match self.autotuners.get_state(ch):
|
||||
case PIDAutotuneState.STATE_OFF | PIDAutotuneState.STATE_FAILED:
|
||||
self.autotuners.load_params_and_set_ready(ch)
|
||||
|
||||
case (
|
||||
PIDAutotuneState.STATE_READY
|
||||
| PIDAutotuneState.STATE_RELAY_STEP_UP
|
||||
| PIDAutotuneState.STATE_RELAY_STEP_DOWN
|
||||
):
|
||||
await self.autotuners.stop_pid_from_running(ch)
|
||||
|
||||
|
@ -5,6 +5,7 @@ from pglive.sources.live_plot import LiveLinePlot
|
||||
from pglive.sources.live_axis import LiveAxis
|
||||
from collections import deque
|
||||
import pyqtgraph as pg
|
||||
from pytec.gui.model.thermostat import ThermostatConnectionState
|
||||
|
||||
pg.setConfigOptions(antialias=True)
|
||||
|
||||
@ -16,6 +17,9 @@ class LiveDataPlotter(QObject):
|
||||
|
||||
self._thermostat.report_update.connect(self.update_report)
|
||||
self._thermostat.pid_update.connect(self.update_pid)
|
||||
self._thermostat.connection_state_changed.connect(
|
||||
self.thermostat_state_change_handler
|
||||
)
|
||||
|
||||
self.NUM_CHANNELS = len(live_plots)
|
||||
self.graphs = []
|
||||
@ -25,6 +29,11 @@ class LiveDataPlotter(QObject):
|
||||
live_plot[1].setTitle(f"Channel {i} Current")
|
||||
self.graphs.append(_TecGraphs(live_plot[0], live_plot[1]))
|
||||
|
||||
@pyqtSlot(ThermostatConnectionState)
|
||||
def thermostat_state_change_handler(self, state):
|
||||
if state == ThermostatConnectionState.DISCONNECTED:
|
||||
self.clear_graphs()
|
||||
|
||||
def _config_connector_max_pts(self, connector, samples):
|
||||
connector.max_points = samples
|
||||
connector.x = deque(maxlen=int(connector.max_points))
|
||||
|
@ -3,6 +3,7 @@ from PyQt6 import QtWidgets, QtGui, QtCore
|
||||
from PyQt6.QtCore import pyqtSignal, pyqtSlot, QSignalBlocker
|
||||
from qasync import asyncSlot
|
||||
from pytec.gui.view.net_settings_input_diag import NetSettingsInputDiag
|
||||
from pytec.gui.model.thermostat import ThermostatConnectionState
|
||||
|
||||
|
||||
class ThermostatCtrlMenu(QtWidgets.QMenu):
|
||||
@ -15,6 +16,9 @@ class ThermostatCtrlMenu(QtWidgets.QMenu):
|
||||
|
||||
self.hw_rev_data = dict()
|
||||
self._thermostat.hw_rev_update.connect(self.hw_rev)
|
||||
self._thermostat.connection_state_changed.connect(
|
||||
self.thermostat_state_change_handler
|
||||
)
|
||||
|
||||
self.fan_group = QtWidgets.QWidget()
|
||||
self.fan_group.setEnabled(False)
|
||||
@ -144,6 +148,12 @@ class ThermostatCtrlMenu(QtWidgets.QMenu):
|
||||
self.fan_pwm_warning.setPixmap(QtGui.QPixmap())
|
||||
self.fan_pwm_warning.setToolTip("")
|
||||
|
||||
@pyqtSlot(ThermostatConnectionState)
|
||||
def thermostat_state_change_handler(self, state):
|
||||
if state == ThermostatConnectionState.DISCONNECTED:
|
||||
self.fan_pwm_warning.setPixmap(QtGui.QPixmap())
|
||||
self.fan_pwm_warning.setToolTip("")
|
||||
|
||||
@pyqtSlot("QVariantMap")
|
||||
def hw_rev(self, hw_rev):
|
||||
self.hw_rev_data = hw_rev
|
||||
|
@ -69,12 +69,10 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.info_box.display_info_box(
|
||||
"Connection Error", "Thermostat connection lost. Is it unplugged?"
|
||||
)
|
||||
self.thermostat.end_session()
|
||||
|
||||
self.thermostat.connection_error.connect(handle_connection_error)
|
||||
|
||||
self.thermostat.connection_error.connect(self.thermostat.timed_out)
|
||||
self.thermostat.connection_error.connect(self.thermostat.end_session)
|
||||
|
||||
self.thermostat.connection_state_changed.connect(self._on_connection_changed)
|
||||
|
||||
self.autotuners = PIDAutoTuner(self, self.thermostat, 2)
|
||||
@ -99,7 +97,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.zero_limits_warning.set_limits_warning
|
||||
)
|
||||
|
||||
self.thermostat.hw_rev_update.connect(self._status)
|
||||
self.report_apply_btn.clicked.connect(
|
||||
lambda: self.thermostat.set_update_s(self.report_refresh_spin.value())
|
||||
)
|
||||
@ -115,7 +112,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.plot_options_menu = PlotOptionsMenu(self.channel_graphs)
|
||||
self.plot_settings.setMenu(self.plot_options_menu)
|
||||
|
||||
self.conn_menu = ConnMenu()
|
||||
self.conn_menu = ConnMenu(self.thermostat, self.connect_btn)
|
||||
self.connect_btn.setMenu(self.conn_menu)
|
||||
|
||||
self.thermostat_ctrl_menu = ThermostatCtrlMenu(
|
||||
@ -133,52 +130,33 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.connect_btn.click()
|
||||
|
||||
@asyncSlot(ThermostatConnectionState)
|
||||
async def _on_connection_changed(self, result):
|
||||
match result:
|
||||
case ThermostatConnectionState.CONNECTED:
|
||||
self.graph_group.setEnabled(True)
|
||||
self.report_group.setEnabled(True)
|
||||
self.thermostat_settings.setEnabled(True)
|
||||
async def _on_connection_changed(self, state):
|
||||
self.graph_group.setEnabled(state == ThermostatConnectionState.CONNECTED)
|
||||
self.report_group.setEnabled(state == ThermostatConnectionState.CONNECTED)
|
||||
self.thermostat_settings.setEnabled(
|
||||
state == ThermostatConnectionState.CONNECTED
|
||||
)
|
||||
|
||||
self.conn_menu.host_set_line.setEnabled(False)
|
||||
self.conn_menu.port_set_spin.setEnabled(False)
|
||||
match state:
|
||||
case ThermostatConnectionState.CONNECTED:
|
||||
self.connect_btn.setText("Disconnect")
|
||||
hw_rev_d = await self.thermostat.get_hw_rev()
|
||||
self.status_lbl.setText(
|
||||
f"Connected to Thermostat v{hw_rev_d['rev']['major']}.{hw_rev_d['rev']['minor']}"
|
||||
)
|
||||
|
||||
case ThermostatConnectionState.CONNECTING:
|
||||
self.status_lbl.setText("Connecting...")
|
||||
self.connect_btn.setText("Stop")
|
||||
self.conn_menu.host_set_line.setEnabled(False)
|
||||
self.conn_menu.port_set_spin.setEnabled(False)
|
||||
self.status_lbl.setText("Connecting...")
|
||||
|
||||
case ThermostatConnectionState.DISCONNECTED:
|
||||
self.graph_group.setEnabled(False)
|
||||
self.report_group.setEnabled(False)
|
||||
self.thermostat_settings.setEnabled(False)
|
||||
|
||||
self.conn_menu.host_set_line.setEnabled(True)
|
||||
self.conn_menu.port_set_spin.setEnabled(True)
|
||||
self.connect_btn.setText("Connect")
|
||||
|
||||
self.status_lbl.setText("Disconnected")
|
||||
|
||||
self.background_task_lbl.setText("Ready.")
|
||||
self.loading_spinner.hide()
|
||||
self.loading_spinner.stop()
|
||||
self.thermostat_ctrl_menu.fan_pwm_warning.setPixmap(QtGui.QPixmap())
|
||||
self.thermostat_ctrl_menu.fan_pwm_warning.setToolTip("")
|
||||
self.channel_graphs.clear_graphs()
|
||||
self.report_box.setChecked(False)
|
||||
for ch in range(self.NUM_CHANNELS):
|
||||
if self.autotuners.get_state(ch) != PIDAutotuneState.STATE_OFF:
|
||||
if self.thermostat.connection_errored:
|
||||
# Don't send any commands, just reset local state
|
||||
self.autotuners.autotuners[ch].setOff()
|
||||
else:
|
||||
await self.autotuners.stop_pid_from_running(ch)
|
||||
|
||||
def _status(self, hw_rev_d: dict):
|
||||
self.status_lbl.setText(
|
||||
f"Connected to Thermostat v{hw_rev_d['rev']['major']}.{hw_rev_d['rev']['minor']}"
|
||||
)
|
||||
|
||||
@asyncSlot(int)
|
||||
async def on_report_box_stateChanged(self, enabled):
|
||||
@ -213,40 +191,25 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
elif self._connecting_task is not None:
|
||||
self._connecting_task.cancel()
|
||||
else:
|
||||
for ch in range(self.NUM_CHANNELS):
|
||||
if self.autotuners.get_state(ch) != PIDAutotuneState.STATE_OFF:
|
||||
if self.thermostat.connection_errored:
|
||||
# Don't send any commands, just reset local state
|
||||
self.autotuners.autotuners[ch].setOff()
|
||||
else:
|
||||
await self.autotuners.stop_pid_from_running(ch)
|
||||
await self.thermostat.end_session()
|
||||
|
||||
@asyncSlot(int, PIDAutotuneState)
|
||||
async def pid_autotune_handler(self, _ch, _state):
|
||||
ch_tuning = []
|
||||
for ch in range(self.NUM_CHANNELS):
|
||||
match self.autotuners.get_state(ch):
|
||||
case PIDAutotuneState.STATE_OFF:
|
||||
self.ctrl_panel_view.change_params_title(
|
||||
ch, ("PID Config", "PID Auto Tune", "Run"), "Run"
|
||||
)
|
||||
case (
|
||||
PIDAutotuneState.STATE_READY
|
||||
| PIDAutotuneState.STATE_RELAY_STEP_UP
|
||||
| PIDAutotuneState.STATE_RELAY_STEP_DOWN
|
||||
):
|
||||
self.ctrl_panel_view.change_params_title(
|
||||
ch, ("PID Config", "PID Auto Tune", "Run"), "Stop"
|
||||
)
|
||||
ch_tuning.append(ch)
|
||||
|
||||
case PIDAutotuneState.STATE_SUCCEEDED:
|
||||
self.info_box.display_info_box(
|
||||
"PID Autotune Success",
|
||||
f"Channel {ch} PID Config has been loaded to Thermostat. Regulating temperature.",
|
||||
)
|
||||
self.info_box.show()
|
||||
|
||||
case PIDAutotuneState.STATE_FAILED:
|
||||
self.info_box.display_info_box(
|
||||
"PID Autotune Failed",
|
||||
f"Channel {ch} PID Autotune has failed.",
|
||||
)
|
||||
self.info_box.show()
|
||||
if self.autotuners.get_state(ch) in {
|
||||
PIDAutotuneState.STATE_READY,
|
||||
PIDAutotuneState.STATE_RELAY_STEP_UP,
|
||||
PIDAutotuneState.STATE_RELAY_STEP_DOWN,
|
||||
}:
|
||||
ch_tuning.append(ch)
|
||||
|
||||
if len(ch_tuning) == 0:
|
||||
self.background_task_lbl.setText("Ready.")
|
||||
|
Loading…
Reference in New Issue
Block a user