forked from M-Labs/thermostat
Compare commits
24 Commits
70627342f3
...
b7cc9db03c
Author | SHA1 | Date | |
---|---|---|---|
b7cc9db03c | |||
92d574a0e3 | |||
2d3bc96da9 | |||
7efd184496 | |||
14621e11a6 | |||
cfe9ee3985 | |||
a60f55d1c8 | |||
a466e3e09d | |||
b0a4e1218a | |||
d199770de1 | |||
9911f72059 | |||
f62adbbc1a | |||
c416fd8b2b | |||
b3ba577268 | |||
3c1228e8a8 | |||
23653a1ecd | |||
29221ce570 | |||
2334a922f7 | |||
1aab3ca1d6 | |||
41d4154a28 | |||
6473905488 | |||
b13f481381 | |||
66303efd11 | |||
f0e7488682 |
@ -14,6 +14,7 @@ class ThermostatConnectionState(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class Thermostat(QObject, metaclass=PropertyMeta):
|
class Thermostat(QObject, metaclass=PropertyMeta):
|
||||||
|
connection_state = Property(ThermostatConnectionState)
|
||||||
hw_rev = Property(dict)
|
hw_rev = Property(dict)
|
||||||
fan = Property(dict)
|
fan = Property(dict)
|
||||||
thermistor = Property(list)
|
thermistor = Property(list)
|
||||||
@ -23,24 +24,44 @@ class Thermostat(QObject, metaclass=PropertyMeta):
|
|||||||
report = Property(list)
|
report = Property(list)
|
||||||
|
|
||||||
connection_error = pyqtSignal()
|
connection_error = pyqtSignal()
|
||||||
connection_state_changed = pyqtSignal(ThermostatConnectionState)
|
|
||||||
|
NUM_CHANNELS = 2
|
||||||
|
|
||||||
def __init__(self, parent, update_s, disconnect_cb=None):
|
def __init__(self, parent, update_s, disconnect_cb=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
self._update_s = update_s
|
self._update_s = update_s
|
||||||
self._client = AsyncioClient()
|
self._client = AsyncioClient()
|
||||||
self._watch_task = None
|
self._watch_task = None
|
||||||
self._report_mode_task = None
|
|
||||||
self._poll_for_report = True
|
|
||||||
self._update_params_task = None
|
self._update_params_task = None
|
||||||
self.disconnect_cb = disconnect_cb
|
self.disconnect_cb = disconnect_cb
|
||||||
super().__init__(parent)
|
self.connection_state = ThermostatConnectionState.DISCONNECTED
|
||||||
|
|
||||||
async def start_session(self, host, port):
|
async def start_session(self, host, port):
|
||||||
self.connection_state_changed.emit(ThermostatConnectionState.CONNECTING)
|
|
||||||
await self._client.connect(host, port)
|
await self._client.connect(host, port)
|
||||||
self.hw_rev = await self.get_hw_rev()
|
self.hw_rev = await self._client.hw_rev()
|
||||||
self.connection_state_changed.emit(ThermostatConnectionState.CONNECTED)
|
|
||||||
self.start_watching()
|
@asyncSlot()
|
||||||
|
async def end_session(self):
|
||||||
|
self.stop_watching()
|
||||||
|
|
||||||
|
if self.disconnect_cb is not None:
|
||||||
|
if asyncio.iscoroutinefunction(self.disconnect_cb):
|
||||||
|
await self.disconnect_cb()
|
||||||
|
else:
|
||||||
|
self.disconnect_cb()
|
||||||
|
|
||||||
|
await self._client.disconnect()
|
||||||
|
|
||||||
|
def start_watching(self):
|
||||||
|
self._watch_task = asyncio.create_task(self.run())
|
||||||
|
|
||||||
|
def stop_watching(self):
|
||||||
|
if self._watch_task is not None:
|
||||||
|
self._watch_task.cancel()
|
||||||
|
self._watch_task = None
|
||||||
|
self._update_params_task.cancel()
|
||||||
|
self._update_params_task = None
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
self._update_params_task = asyncio.create_task(self.update_params())
|
self._update_params_task = asyncio.create_task(self.update_params())
|
||||||
@ -53,28 +74,16 @@ class Thermostat(QObject, metaclass=PropertyMeta):
|
|||||||
"Encountered an error while polling for information from Thermostat.",
|
"Encountered an error while polling for information from Thermostat.",
|
||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
await self.handle_connection_error()
|
await self.end_session()
|
||||||
|
self.connection_state = ThermostatConnectionState.DISCONNECTED
|
||||||
|
self.connection_error.emit()
|
||||||
return
|
return
|
||||||
self._update_params_task = asyncio.create_task(self.update_params())
|
self._update_params_task = asyncio.create_task(self.update_params())
|
||||||
await asyncio.sleep(self._update_s)
|
await asyncio.sleep(self._update_s)
|
||||||
|
|
||||||
async def handle_connection_error(self):
|
|
||||||
await self.end_session()
|
|
||||||
self.connection_error.emit()
|
|
||||||
|
|
||||||
async def get_hw_rev(self):
|
|
||||||
return await self._client.hw_rev()
|
|
||||||
|
|
||||||
async def update_params(self):
|
async def update_params(self):
|
||||||
if self._poll_for_report:
|
self.fan, self.pwm, self.report, self.pid, self.thermistor, self.postfilter = (
|
||||||
(
|
await asyncio.gather(
|
||||||
self.fan,
|
|
||||||
self.pwm,
|
|
||||||
self.report,
|
|
||||||
self.pid,
|
|
||||||
self.thermistor,
|
|
||||||
self.postfilter,
|
|
||||||
) = await asyncio.gather(
|
|
||||||
self._client.get_fan(),
|
self._client.get_fan(),
|
||||||
self._client.get_pwm(),
|
self._client.get_pwm(),
|
||||||
self._client.report(),
|
self._client.report(),
|
||||||
@ -82,56 +91,14 @@ class Thermostat(QObject, metaclass=PropertyMeta):
|
|||||||
self._client.get_steinhart_hart(),
|
self._client.get_steinhart_hart(),
|
||||||
self._client.get_postfilter(),
|
self._client.get_postfilter(),
|
||||||
)
|
)
|
||||||
else:
|
)
|
||||||
self.fan, self.pwm, self.pid, self.thermistor, self.postfilter = (
|
|
||||||
await asyncio.gather(
|
|
||||||
self._client.get_fan(),
|
|
||||||
self._client.get_pwm(),
|
|
||||||
self._client.get_pid(),
|
|
||||||
self._client.get_steinhart_hart(),
|
|
||||||
self._client.get_postfilter(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def connected(self):
|
def connected(self):
|
||||||
return self._client.connected()
|
return self._client.connected()
|
||||||
|
|
||||||
def start_watching(self):
|
@pyqtSlot(float)
|
||||||
self._watch_task = asyncio.create_task(self.run())
|
def set_update_s(self, update_s):
|
||||||
|
self._update_s = update_s
|
||||||
@asyncSlot()
|
|
||||||
async def stop_watching(self):
|
|
||||||
if self._watch_task is not None:
|
|
||||||
await self.set_report_mode(False)
|
|
||||||
self._watch_task.cancel()
|
|
||||||
self._watch_task = None
|
|
||||||
self._update_params_task.cancel()
|
|
||||||
self._update_params_task = None
|
|
||||||
|
|
||||||
async def set_report_mode(self, enabled: bool):
|
|
||||||
self._poll_for_report = not enabled
|
|
||||||
if enabled:
|
|
||||||
self._report_mode_task = asyncio.create_task(self.report_mode())
|
|
||||||
else:
|
|
||||||
self._client.stop_report_mode()
|
|
||||||
|
|
||||||
async def report_mode(self):
|
|
||||||
async for report in self._client.report_mode():
|
|
||||||
self.report_update.emit(report)
|
|
||||||
|
|
||||||
@asyncSlot()
|
|
||||||
async def end_session(self):
|
|
||||||
await self.set_report_mode(False)
|
|
||||||
self.stop_watching()
|
|
||||||
|
|
||||||
if self.disconnect_cb is not None:
|
|
||||||
if asyncio.iscoroutinefunction(self.disconnect_cb):
|
|
||||||
await self.disconnect_cb()
|
|
||||||
else:
|
|
||||||
self.disconnect_cb()
|
|
||||||
|
|
||||||
await self._client.disconnect()
|
|
||||||
self.connection_state_changed.emit(ThermostatConnectionState.DISCONNECTED)
|
|
||||||
|
|
||||||
async def set_ipv4(self, ipv4):
|
async def set_ipv4(self, ipv4):
|
||||||
await self._client.set_param("ipv4", ipv4)
|
await self._client.set_param("ipv4", ipv4)
|
||||||
@ -153,10 +120,6 @@ class Thermostat(QObject, metaclass=PropertyMeta):
|
|||||||
async def reset(self):
|
async def reset(self):
|
||||||
await self._client.reset()
|
await self._client.reset()
|
||||||
|
|
||||||
@pyqtSlot(float)
|
|
||||||
def set_update_s(self, update_s):
|
|
||||||
self._update_s = update_s
|
|
||||||
|
|
||||||
async def set_fan(self, power="auto"):
|
async def set_fan(self, power="auto"):
|
||||||
await self._client.set_fan(power)
|
await self._client.set_fan(power)
|
||||||
|
|
||||||
|
@ -3,12 +3,12 @@ from PyQt6.QtCore import pyqtSlot
|
|||||||
from pytec.gui.model.thermostat import ThermostatConnectionState
|
from pytec.gui.model.thermostat import ThermostatConnectionState
|
||||||
|
|
||||||
|
|
||||||
class ConnMenu(QtWidgets.QMenu):
|
class ConnectionDetailsMenu(QtWidgets.QMenu):
|
||||||
def __init__(self, thermostat, connect_btn):
|
def __init__(self, thermostat, connect_btn):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._thermostat = thermostat
|
self._thermostat = thermostat
|
||||||
self._connect_btn = connect_btn
|
self._connect_btn = connect_btn
|
||||||
self._thermostat.connection_state_changed.connect(
|
self._thermostat.connection_state_update.connect(
|
||||||
self.thermostat_state_change_handler
|
self.thermostat_state_change_handler
|
||||||
)
|
)
|
||||||
|
|
@ -46,8 +46,6 @@ registerParameterType("mutex", MutexParameter)
|
|||||||
|
|
||||||
|
|
||||||
class CtrlPanel(QObject):
|
class CtrlPanel(QObject):
|
||||||
set_zero_limits_warning_sig = pyqtSignal(list)
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
thermostat,
|
thermostat,
|
||||||
@ -231,8 +229,6 @@ class CtrlPanel(QObject):
|
|||||||
|
|
||||||
@pyqtSlot("QVariantList")
|
@pyqtSlot("QVariantList")
|
||||||
def update_pwm(self, pwm_data):
|
def update_pwm(self, pwm_data):
|
||||||
channels_zeroed_limits = [set() for i in range(self.NUM_CHANNELS)]
|
|
||||||
|
|
||||||
for pwm_params in pwm_data:
|
for pwm_params in pwm_data:
|
||||||
channel = pwm_params["channel"]
|
channel = pwm_params["channel"]
|
||||||
with QSignalBlocker(self.params[channel]):
|
with QSignalBlocker(self.params[channel]):
|
||||||
@ -246,11 +242,6 @@ class CtrlPanel(QObject):
|
|||||||
"Output Config", "Limits", "Max Heating Current"
|
"Output Config", "Limits", "Max Heating Current"
|
||||||
).setValue(pwm_params["max_i_neg"]["value"] * 1000)
|
).setValue(pwm_params["max_i_neg"]["value"] * 1000)
|
||||||
|
|
||||||
for limit in "max_i_pos", "max_i_neg", "max_v":
|
|
||||||
if pwm_params[limit]["value"] == 0.0:
|
|
||||||
channels_zeroed_limits[channel].add(limit)
|
|
||||||
self.set_zero_limits_warning_sig.emit(channels_zeroed_limits)
|
|
||||||
|
|
||||||
@pyqtSlot("QVariantList")
|
@pyqtSlot("QVariantList")
|
||||||
def update_postfilter(self, postfilter_data):
|
def update_postfilter(self, postfilter_data):
|
||||||
for postfilter_params in postfilter_data:
|
for postfilter_params in postfilter_data:
|
||||||
|
@ -17,7 +17,7 @@ class LiveDataPlotter(QObject):
|
|||||||
|
|
||||||
self._thermostat.report_update.connect(self.update_report)
|
self._thermostat.report_update.connect(self.update_report)
|
||||||
self._thermostat.pid_update.connect(self.update_pid)
|
self._thermostat.pid_update.connect(self.update_pid)
|
||||||
self._thermostat.connection_state_changed.connect(
|
self._thermostat.connection_state_update.connect(
|
||||||
self.thermostat_state_change_handler
|
self.thermostat_state_change_handler
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -369,7 +369,7 @@
|
|||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="report_layout" stretch="0,1,1,1">
|
<layout class="QHBoxLayout" name="report_layout" stretch="0,1,1">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>6</number>
|
<number>6</number>
|
||||||
</property>
|
</property>
|
||||||
@ -435,31 +435,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="report_box">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>80</width>
|
|
||||||
<height>16777215</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="baseSize">
|
|
||||||
<size>
|
|
||||||
<width>80</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Report</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="report_apply_btn">
|
<widget class="QPushButton" name="report_apply_btn">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
|
@ -6,7 +6,7 @@ from pytec.gui.view.net_settings_input_diag import NetSettingsInputDiag
|
|||||||
from pytec.gui.model.thermostat import ThermostatConnectionState
|
from pytec.gui.model.thermostat import ThermostatConnectionState
|
||||||
|
|
||||||
|
|
||||||
class ThermostatCtrlMenu(QtWidgets.QMenu):
|
class ThermostatSettingsMenu(QtWidgets.QMenu):
|
||||||
def __init__(self, thermostat, info_box, style):
|
def __init__(self, thermostat, info_box, style):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._thermostat = thermostat
|
self._thermostat = thermostat
|
||||||
@ -16,7 +16,7 @@ class ThermostatCtrlMenu(QtWidgets.QMenu):
|
|||||||
|
|
||||||
self.hw_rev_data = dict()
|
self.hw_rev_data = dict()
|
||||||
self._thermostat.hw_rev_update.connect(self.hw_rev)
|
self._thermostat.hw_rev_update.connect(self.hw_rev)
|
||||||
self._thermostat.connection_state_changed.connect(
|
self._thermostat.connection_state_update.connect(
|
||||||
self.thermostat_state_change_handler
|
self.thermostat_state_change_handler
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -188,6 +188,7 @@ class ThermostatCtrlMenu(QtWidgets.QMenu):
|
|||||||
|
|
||||||
await self._thermostat.reset()
|
await self._thermostat.reset()
|
||||||
await self._thermostat.end_session()
|
await self._thermostat.end_session()
|
||||||
|
self._thermostat.connection_state = ThermostatConnectionState.DISCONNECTED
|
||||||
|
|
||||||
@asyncSlot(bool)
|
@asyncSlot(bool)
|
||||||
async def dfu_request(self, _):
|
async def dfu_request(self, _):
|
||||||
@ -195,6 +196,7 @@ class ThermostatCtrlMenu(QtWidgets.QMenu):
|
|||||||
|
|
||||||
await self._thermostat.dfu()
|
await self._thermostat.dfu()
|
||||||
await self._thermostat.end_session()
|
await self._thermostat.end_session()
|
||||||
|
self._thermostat.connection_state = ThermostatConnectionState.DISCONNECTED
|
||||||
|
|
||||||
@asyncSlot(bool)
|
@asyncSlot(bool)
|
||||||
async def net_settings_request(self, _):
|
async def net_settings_request(self, _):
|
||||||
@ -209,4 +211,5 @@ class ThermostatCtrlMenu(QtWidgets.QMenu):
|
|||||||
assert self._thermostat.connected()
|
assert self._thermostat.connected()
|
||||||
|
|
||||||
await self._thermostat.set_ipv4(ipv4_settings)
|
await self._thermostat.set_ipv4(ipv4_settings)
|
||||||
await self._thermostat.end_session()
|
await self._thermostat.end_session()
|
||||||
|
self._thermostat.connection_state = ThermostatConnectionState.DISCONNECTED
|
@ -3,15 +3,24 @@ from PyQt6 import QtWidgets, QtGui
|
|||||||
|
|
||||||
|
|
||||||
class ZeroLimitsWarningView(QObject):
|
class ZeroLimitsWarningView(QObject):
|
||||||
def __init__(self, style, limit_warning):
|
def __init__(self, thermostat, style, limit_warning):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self._thermostat = thermostat
|
||||||
|
self._thermostat.pwm_update.connect(self.set_limits_warning)
|
||||||
self._lbl = limit_warning
|
self._lbl = limit_warning
|
||||||
self._style = style
|
self._style = style
|
||||||
|
|
||||||
@pyqtSlot("QVariantList")
|
@pyqtSlot("QVariantList")
|
||||||
def set_limits_warning(self, channels_zeroed_limits: list):
|
def set_limits_warning(self, pwm_data: list):
|
||||||
channel_disabled = [False, False]
|
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:
|
||||||
|
channels_zeroed_limits[channel].add(limit)
|
||||||
|
|
||||||
|
channel_disabled = [False, False]
|
||||||
report_str = "The following output limit(s) are set to zero:\n"
|
report_str = "The following output limit(s) are set to zero:\n"
|
||||||
for ch, zeroed_limits in enumerate(channels_zeroed_limits):
|
for ch, zeroed_limits in enumerate(channels_zeroed_limits):
|
||||||
if {"max_i_pos", "max_i_neg"}.issubset(zeroed_limits):
|
if {"max_i_pos", "max_i_neg"}.issubset(zeroed_limits):
|
167
pytec/tec_qt.py
167
pytec/tec_qt.py
@ -1,22 +1,24 @@
|
|||||||
from pytec.gui.view.zero_limits_warning import ZeroLimitsWarningView
|
"""GUI for the Sinara 8451 Thermostat"""
|
||||||
from pytec.gui.view.thermostat_ctrl_menu import ThermostatCtrlMenu
|
|
||||||
from pytec.gui.view.conn_menu import ConnMenu
|
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
|
||||||
from pytec.gui.view.plot_options_menu import PlotOptionsMenu
|
from pytec.gui.view.plot_options_menu import PlotOptionsMenu
|
||||||
from pytec.gui.view.live_plot_view import LiveDataPlotter
|
from pytec.gui.view.live_plot_view import LiveDataPlotter
|
||||||
from pytec.gui.view.ctrl_panel import CtrlPanel
|
from pytec.gui.view.ctrl_panel import CtrlPanel
|
||||||
from pytec.gui.view.info_box import InfoBox
|
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():
|
def get_argparser():
|
||||||
@ -56,86 +58,92 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
ui_file_path = importlib.resources.files("pytec.gui.view").joinpath("tec_qt.ui")
|
ui_file_path = importlib.resources.files("pytec.gui.view").joinpath("tec_qt.ui")
|
||||||
uic.loadUi(ui_file_path, self)
|
uic.loadUi(ui_file_path, self)
|
||||||
|
|
||||||
self.info_box = InfoBox()
|
self._info_box = InfoBox()
|
||||||
|
|
||||||
# Models
|
# Models
|
||||||
self.thermostat = Thermostat(self, self.report_refresh_spin.value())
|
self._thermostat = Thermostat(self, self.report_refresh_spin.value())
|
||||||
self._connecting_task = None
|
self._connecting_task = None
|
||||||
self.thermostat.connection_state_changed.connect(self._on_connection_changed)
|
self._thermostat.connection_state_update.connect(
|
||||||
|
self._on_connection_state_changed
|
||||||
|
)
|
||||||
|
|
||||||
self.autotuners = PIDAutoTuner(self, self.thermostat, 2)
|
self._autotuners = PIDAutoTuner(self, self._thermostat, 2)
|
||||||
self.autotuners.autotune_state_changed.connect(self.pid_autotune_handler)
|
self._autotuners.autotune_state_changed.connect(
|
||||||
|
self._on_pid_autotune_state_changed
|
||||||
|
)
|
||||||
|
|
||||||
# Handlers for disconnections
|
# Handlers for disconnections
|
||||||
async def autotune_disconnect():
|
async def autotune_disconnect():
|
||||||
for ch in range(self.NUM_CHANNELS):
|
for ch in range(self.NUM_CHANNELS):
|
||||||
if self.autotuners.get_state(ch) != PIDAutotuneState.STATE_OFF:
|
if self._autotuners.get_state(ch) != PIDAutotuneState.STATE_OFF:
|
||||||
await self.autotuners.stop_pid_from_running(ch)
|
await self._autotuners.stop_pid_from_running(ch)
|
||||||
self.thermostat.disconnect_cb = autotune_disconnect
|
|
||||||
|
self._thermostat.disconnect_cb = autotune_disconnect
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def handle_connection_error():
|
def handle_connection_error():
|
||||||
self.info_box.display_info_box(
|
self._info_box.display_info_box(
|
||||||
"Connection Error", "Thermostat connection lost. Is it unplugged?"
|
"Connection Error", "Thermostat connection lost. Is it unplugged?"
|
||||||
)
|
)
|
||||||
self.thermostat.connection_error.connect(handle_connection_error)
|
|
||||||
|
self._thermostat.connection_error.connect(handle_connection_error)
|
||||||
|
|
||||||
# Control Panel
|
# Control Panel
|
||||||
def get_ctrl_panel_config(args):
|
def get_ctrl_panel_config(args):
|
||||||
with open(args.param_tree, "r", encoding="utf-8") as f:
|
with open(args.param_tree, "r", encoding="utf-8") as f:
|
||||||
return json.load(f)["ctrl_panel"]
|
return json.load(f)["ctrl_panel"]
|
||||||
|
|
||||||
self.ctrl_panel_view = CtrlPanel(
|
self._ctrl_panel_view = CtrlPanel(
|
||||||
self.thermostat,
|
self._thermostat,
|
||||||
self.autotuners,
|
self._autotuners,
|
||||||
self.info_box,
|
self._info_box,
|
||||||
[self.ch0_tree, self.ch1_tree],
|
[self.ch0_tree, self.ch1_tree],
|
||||||
get_ctrl_panel_config(args),
|
get_ctrl_panel_config(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Graphs
|
# Graphs
|
||||||
self.channel_graphs = LiveDataPlotter(
|
self._channel_graphs = LiveDataPlotter(
|
||||||
self.thermostat,
|
self._thermostat,
|
||||||
[
|
[
|
||||||
[getattr(self, f"ch{ch}_t_graph"), getattr(self, f"ch{ch}_i_graph")]
|
[getattr(self, f"ch{ch}_t_graph"), getattr(self, f"ch{ch}_i_graph")]
|
||||||
for ch in range(self.NUM_CHANNELS)
|
for ch in range(self.NUM_CHANNELS)
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Bottom bar menus
|
# Bottom bar menus
|
||||||
self.conn_menu = ConnMenu(self.thermostat, self.connect_btn)
|
self.connection_details_menu = ConnectionDetailsMenu(
|
||||||
self.connect_btn.setMenu(self.conn_menu)
|
self._thermostat, self.connect_btn
|
||||||
|
|
||||||
self.thermostat_ctrl_menu = ThermostatCtrlMenu(
|
|
||||||
self.thermostat, self.info_box, self.style()
|
|
||||||
)
|
)
|
||||||
self.thermostat_settings.setMenu(self.thermostat_ctrl_menu)
|
self.connect_btn.setMenu(self.connection_details_menu)
|
||||||
|
|
||||||
self.plot_options_menu = PlotOptionsMenu(self.channel_graphs)
|
self._thermostat_settings_menu = ThermostatSettingsMenu(
|
||||||
self.plot_settings.setMenu(self.plot_options_menu)
|
self._thermostat, self._info_box, self.style()
|
||||||
|
)
|
||||||
|
self.thermostat_settings.setMenu(self._thermostat_settings_menu)
|
||||||
|
|
||||||
|
self._plot_options_menu = PlotOptionsMenu(self._channel_graphs)
|
||||||
|
self.plot_settings.setMenu(self._plot_options_menu)
|
||||||
|
|
||||||
# Status line
|
# Status line
|
||||||
self.zero_limits_warning = ZeroLimitsWarningView(
|
self._zero_limits_warning_view = ZeroLimitsWarningView(
|
||||||
self.style(), self.limits_warning
|
self._thermostat, self.style(), self.limits_warning
|
||||||
)
|
|
||||||
self.ctrl_panel_view.set_zero_limits_warning_sig.connect(
|
|
||||||
self.zero_limits_warning.set_limits_warning
|
|
||||||
)
|
)
|
||||||
self.loading_spinner.hide()
|
self.loading_spinner.hide()
|
||||||
|
|
||||||
self.report_apply_btn.clicked.connect(
|
self.report_apply_btn.clicked.connect(
|
||||||
lambda: self.thermostat.set_update_s(self.report_refresh_spin.value())
|
lambda: self._thermostat.set_update_s(self.report_refresh_spin.value())
|
||||||
)
|
)
|
||||||
|
|
||||||
@asyncClose
|
@asyncClose
|
||||||
async def closeEvent(self, _event):
|
async def closeEvent(self, _event):
|
||||||
try:
|
try:
|
||||||
await self.thermostat.end_session()
|
await self._thermostat.end_session()
|
||||||
|
self._thermostat.connection_state = ThermostatConnectionState.DISCONNECTED
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@pyqtSlot(ThermostatConnectionState)
|
@pyqtSlot(ThermostatConnectionState)
|
||||||
def _on_connection_changed(self, state):
|
def _on_connection_state_changed(self, state):
|
||||||
self.graph_group.setEnabled(state == ThermostatConnectionState.CONNECTED)
|
self.graph_group.setEnabled(state == ThermostatConnectionState.CONNECTED)
|
||||||
self.thermostat_settings.setEnabled(
|
self.thermostat_settings.setEnabled(
|
||||||
state == ThermostatConnectionState.CONNECTED
|
state == ThermostatConnectionState.CONNECTED
|
||||||
@ -147,8 +155,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.connect_btn.setText("Disconnect")
|
self.connect_btn.setText("Disconnect")
|
||||||
self.status_lbl.setText(
|
self.status_lbl.setText(
|
||||||
"Connected to Thermostat v"
|
"Connected to Thermostat v"
|
||||||
f"{self.thermostat.hw_rev['rev']['major']}."
|
f"{self._thermostat.hw_rev['rev']['major']}."
|
||||||
f"{self.thermostat.hw_rev['rev']['minor']}"
|
f"{self._thermostat.hw_rev['rev']['minor']}"
|
||||||
)
|
)
|
||||||
|
|
||||||
case ThermostatConnectionState.CONNECTING:
|
case ThermostatConnectionState.CONNECTING:
|
||||||
@ -158,57 +166,56 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
case ThermostatConnectionState.DISCONNECTED:
|
case ThermostatConnectionState.DISCONNECTED:
|
||||||
self.connect_btn.setText("Connect")
|
self.connect_btn.setText("Connect")
|
||||||
self.status_lbl.setText("Disconnected")
|
self.status_lbl.setText("Disconnected")
|
||||||
self.report_box.setChecked(False)
|
|
||||||
|
|
||||||
@pyqtSlot(int, PIDAutotuneState)
|
@pyqtSlot(int, PIDAutotuneState)
|
||||||
def pid_autotune_handler(self, _ch, _state):
|
def _on_pid_autotune_state_changed(self, _ch, _state):
|
||||||
ch_tuning = []
|
autotuning_channels = []
|
||||||
for ch in range(self.NUM_CHANNELS):
|
for ch in range(self.NUM_CHANNELS):
|
||||||
if self.autotuners.get_state(ch) in {
|
if self._autotuners.get_state(ch) in {
|
||||||
PIDAutotuneState.STATE_READY,
|
PIDAutotuneState.STATE_READY,
|
||||||
PIDAutotuneState.STATE_RELAY_STEP_UP,
|
PIDAutotuneState.STATE_RELAY_STEP_UP,
|
||||||
PIDAutotuneState.STATE_RELAY_STEP_DOWN,
|
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.background_task_lbl.setText("Ready.")
|
||||||
self.loading_spinner.hide()
|
self.loading_spinner.hide()
|
||||||
self.loading_spinner.stop()
|
self.loading_spinner.stop()
|
||||||
else:
|
else:
|
||||||
self.background_task_lbl.setText(
|
self.background_task_lbl.setText(
|
||||||
"Autotuning channel {ch}...".format(ch=ch_tuning)
|
f"Autotuning channel {autotuning_channels}..."
|
||||||
)
|
)
|
||||||
self.loading_spinner.start()
|
self.loading_spinner.start()
|
||||||
self.loading_spinner.show()
|
self.loading_spinner.show()
|
||||||
|
|
||||||
@asyncSlot()
|
@asyncSlot()
|
||||||
async def on_connect_btn_clicked(self):
|
async def on_connect_btn_clicked(self):
|
||||||
if (self._connecting_task is None) and (not self.thermostat.connected()):
|
match self._thermostat.connection_state:
|
||||||
self._connecting_task = asyncio.create_task(
|
case ThermostatConnectionState.DISCONNECTED:
|
||||||
self.thermostat.start_session(
|
self._connecting_task = asyncio.current_task()
|
||||||
host=self.conn_menu.host_set_line.text(),
|
self._thermostat.connection_state = ThermostatConnectionState.CONNECTING
|
||||||
port=self.conn_menu.port_set_spin.value(),
|
await self._thermostat.start_session(
|
||||||
|
host=self.connection_details_menu.host_set_line.text(),
|
||||||
|
port=self.connection_details_menu.port_set_spin.value(),
|
||||||
)
|
)
|
||||||
)
|
|
||||||
try:
|
|
||||||
await self._connecting_task
|
|
||||||
except (OSError, asyncio.CancelledError) as exc:
|
|
||||||
await self.thermostat.end_session()
|
|
||||||
if isinstance(exc, asyncio.CancelledError):
|
|
||||||
return
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
self._connecting_task = None
|
self._connecting_task = None
|
||||||
|
self._thermostat.connection_state = ThermostatConnectionState.CONNECTED
|
||||||
|
self._thermostat.start_watching()
|
||||||
|
|
||||||
elif self._connecting_task is not None:
|
case ThermostatConnectionState.CONNECTING:
|
||||||
self._connecting_task.cancel()
|
self._connecting_task.cancel()
|
||||||
else:
|
self._connecting_task = None
|
||||||
await self.thermostat.end_session()
|
await self._thermostat.end_session()
|
||||||
|
self._thermostat.connection_state = (
|
||||||
|
ThermostatConnectionState.DISCONNECTED
|
||||||
|
)
|
||||||
|
|
||||||
@asyncSlot(int)
|
case ThermostatConnectionState.CONNECTED:
|
||||||
async def on_report_box_stateChanged(self, enabled):
|
await self._thermostat.end_session()
|
||||||
await self.thermostat.set_report_mode(enabled)
|
self._thermostat.connection_state = (
|
||||||
|
ThermostatConnectionState.DISCONNECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def coro_main():
|
async def coro_main():
|
||||||
@ -231,9 +238,9 @@ async def coro_main():
|
|||||||
|
|
||||||
if args.connect:
|
if args.connect:
|
||||||
if args.HOST:
|
if args.HOST:
|
||||||
main_window.conn_menu.host_set_line.setText(args.HOST)
|
main_window.connection_details_menu.host_set_line.setText(args.HOST)
|
||||||
if args.PORT:
|
if args.PORT:
|
||||||
main_window.conn_menu.port_set_spin.setValue(int(args.PORT))
|
main_window.connection_details_menu.port_set_spin.setValue(int(args.PORT))
|
||||||
main_window.connect_btn.click()
|
main_window.connect_btn.click()
|
||||||
|
|
||||||
await app_quit_event.wait()
|
await app_quit_event.wait()
|
||||||
|
Loading…
Reference in New Issue
Block a user