forked from M-Labs/thermostat
Compare commits
13 Commits
425dee173b
...
3f32c9a4a2
Author | SHA1 | Date | |
---|---|---|---|
3f32c9a4a2 | |||
89986fd810 | |||
f8900d295d | |||
f0772a8072 | |||
b9ba1e2d9f | |||
4997db0b4c | |||
65e3f6395b | |||
daa3192d41 | |||
a6be838445 | |||
89076b2d13 | |||
78b3f8e419 | |||
3925551071 | |||
2fd852e171 |
@ -243,11 +243,11 @@ class AsyncioClient:
|
|||||||
await self.disconnect()
|
await self.disconnect()
|
||||||
|
|
||||||
async def dfu(self):
|
async def dfu(self):
|
||||||
"""Put the Thermostat in DFU update mode
|
"""Put the Thermostat in DFU mode
|
||||||
|
|
||||||
The client is disconnected as the Thermostat stops responding to
|
The client is disconnected as the Thermostat stops responding to
|
||||||
TCP commands in DFU update mode. The only way to exit it is by
|
TCP commands in DFU mode. To exit it, submit a DFU leave request
|
||||||
power-cycling.
|
or power-cycle the Thermostat.
|
||||||
"""
|
"""
|
||||||
async with self._command_lock:
|
async with self._command_lock:
|
||||||
self._writer.write("dfu\n".encode("utf-8"))
|
self._writer.write("dfu\n".encode("utf-8"))
|
||||||
|
@ -30,19 +30,6 @@ class PIDAutoTuner(QObject):
|
|||||||
def get_state(self, ch):
|
def get_state(self, ch):
|
||||||
return self.autotuners[ch].state()
|
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):
|
def load_params_and_set_ready(self, ch):
|
||||||
self.autotuners[ch].setParam(
|
self.autotuners[ch].setParam(
|
||||||
self.target_temp[ch],
|
self.target_temp[ch],
|
||||||
|
@ -54,11 +54,15 @@ 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,
|
||||||
)
|
)
|
||||||
self.connection_error.emit()
|
self.handle_connection_error()
|
||||||
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)
|
||||||
|
|
||||||
|
def handle_connection_error(self):
|
||||||
|
self.end_session()
|
||||||
|
self.connection_error.emit()
|
||||||
|
|
||||||
async def get_hw_rev(self):
|
async def get_hw_rev(self):
|
||||||
return await self._client.hw_rev()
|
return await self._client.hw_rev()
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ class CtrlPanel(QObject):
|
|||||||
)
|
)
|
||||||
self.params[i].child(
|
self.params[i].child(
|
||||||
"PID Config", "PID Auto Tune", "Run"
|
"PID Config", "PID Auto Tune", "Run"
|
||||||
).sigActivated.connect(partial(self.autotuners.pid_auto_tune_request, i))
|
).sigActivated.connect(partial(self.pid_auto_tune_request, i))
|
||||||
|
|
||||||
self.thermostat.pid_update.connect(self.update_pid)
|
self.thermostat.pid_update.connect(self.update_pid)
|
||||||
self.thermostat.report_update.connect(self.update_report)
|
self.thermostat.report_update.connect(self.update_report)
|
||||||
@ -285,6 +285,19 @@ class CtrlPanel(QObject):
|
|||||||
f"Channel {ch} PID Autotune has failed.",
|
f"Channel {ch} PID Autotune has failed.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@asyncSlot()
|
||||||
|
async def pid_auto_tune_request(self, ch):
|
||||||
|
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)
|
||||||
|
|
||||||
@asyncSlot(int)
|
@asyncSlot(int)
|
||||||
async def load_settings(self, ch):
|
async def load_settings(self, ch):
|
||||||
await self.thermostat.load_cfg(ch)
|
await self.thermostat.load_cfg(ch)
|
||||||
|
@ -15,9 +15,7 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import argparse
|
import argparse
|
||||||
from PyQt6 import QtWidgets, QtGui, uic
|
from PyQt6 import QtWidgets, QtGui, uic
|
||||||
from PyQt6.QtCore import QSignalBlocker, pyqtSlot
|
from PyQt6.QtCore import pyqtSlot
|
||||||
import pyqtgraph as pg
|
|
||||||
from functools import partial
|
|
||||||
import importlib.resources
|
import importlib.resources
|
||||||
|
|
||||||
|
|
||||||
@ -71,20 +69,19 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.autotuners.autotune_state_changed.connect(self.pid_autotune_handler)
|
self.autotuners.autotune_state_changed.connect(self.pid_autotune_handler)
|
||||||
|
|
||||||
# Handlers for disconnections
|
# Handlers for disconnections
|
||||||
@asyncSlot()
|
|
||||||
async def handle_connection_error():
|
|
||||||
self.info_box.display_info_box(
|
|
||||||
"Connection Error", "Thermostat connection lost. Is it unplugged?"
|
|
||||||
)
|
|
||||||
await self.thermostat.end_session()
|
|
||||||
self.thermostat.connection_error.connect(handle_connection_error)
|
|
||||||
|
|
||||||
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()
|
||||||
|
def handle_connection_error():
|
||||||
|
self.info_box.display_info_box(
|
||||||
|
"Connection Error", "Thermostat connection lost. Is it unplugged?"
|
||||||
|
)
|
||||||
|
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:
|
||||||
@ -126,27 +123,26 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.ctrl_panel_view.set_zero_limits_warning_sig.connect(
|
self.ctrl_panel_view.set_zero_limits_warning_sig.connect(
|
||||||
self.zero_limits_warning.set_limits_warning
|
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())
|
||||||
)
|
)
|
||||||
|
|
||||||
if args.connect:
|
@asyncClose
|
||||||
if args.IP:
|
async def closeEvent(self, _event):
|
||||||
self.host_set_line.setText(args.IP)
|
try:
|
||||||
if args.PORT:
|
await self.thermostat.end_session()
|
||||||
self.port_set_spin.setValue(int(args.PORT))
|
except:
|
||||||
self.connect_btn.click()
|
pass
|
||||||
|
|
||||||
@asyncSlot(ThermostatConnectionState)
|
@pyqtSlot(ThermostatConnectionState)
|
||||||
async def _on_connection_changed(self, state):
|
def _on_connection_changed(self, state):
|
||||||
self.graph_group.setEnabled(state == ThermostatConnectionState.CONNECTED)
|
self.graph_group.setEnabled(state == ThermostatConnectionState.CONNECTED)
|
||||||
self.report_group.setEnabled(state == ThermostatConnectionState.CONNECTED)
|
|
||||||
self.thermostat_settings.setEnabled(
|
self.thermostat_settings.setEnabled(
|
||||||
state == ThermostatConnectionState.CONNECTED
|
state == ThermostatConnectionState.CONNECTED
|
||||||
)
|
)
|
||||||
|
self.report_group.setEnabled(state == ThermostatConnectionState.CONNECTED)
|
||||||
|
|
||||||
match state:
|
match state:
|
||||||
case ThermostatConnectionState.CONNECTED:
|
case ThermostatConnectionState.CONNECTED:
|
||||||
@ -166,16 +162,27 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.status_lbl.setText("Disconnected")
|
self.status_lbl.setText("Disconnected")
|
||||||
self.report_box.setChecked(False)
|
self.report_box.setChecked(False)
|
||||||
|
|
||||||
@asyncSlot(int)
|
@pyqtSlot(int, PIDAutotuneState)
|
||||||
async def on_report_box_stateChanged(self, enabled):
|
def pid_autotune_handler(self, _ch, _state):
|
||||||
await self.thermostat.set_report_mode(enabled)
|
ch_tuning = []
|
||||||
|
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)
|
||||||
|
|
||||||
@asyncClose
|
if len(ch_tuning) == 0:
|
||||||
async def closeEvent(self, _event):
|
self.background_task_lbl.setText("Ready.")
|
||||||
try:
|
self.loading_spinner.hide()
|
||||||
await self.thermostat.end_session()
|
self.loading_spinner.stop()
|
||||||
except:
|
else:
|
||||||
pass
|
self.background_task_lbl.setText(
|
||||||
|
"Autotuning channel {ch}...".format(ch=ch_tuning)
|
||||||
|
)
|
||||||
|
self.loading_spinner.start()
|
||||||
|
self.loading_spinner.show()
|
||||||
|
|
||||||
@asyncSlot()
|
@asyncSlot()
|
||||||
async def on_connect_btn_clicked(self):
|
async def on_connect_btn_clicked(self):
|
||||||
@ -201,27 +208,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
else:
|
else:
|
||||||
await self.thermostat.end_session()
|
await self.thermostat.end_session()
|
||||||
|
|
||||||
@asyncSlot(int, PIDAutotuneState)
|
@asyncSlot(int)
|
||||||
async def pid_autotune_handler(self, _ch, _state):
|
async def on_report_box_stateChanged(self, enabled):
|
||||||
ch_tuning = []
|
await self.thermostat.set_report_mode(enabled)
|
||||||
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)
|
|
||||||
|
|
||||||
if len(ch_tuning) == 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)
|
|
||||||
)
|
|
||||||
self.loading_spinner.start()
|
|
||||||
self.loading_spinner.show()
|
|
||||||
|
|
||||||
|
|
||||||
async def coro_main():
|
async def coro_main():
|
||||||
@ -242,6 +231,13 @@ async def coro_main():
|
|||||||
main_window = MainWindow(args)
|
main_window = MainWindow(args)
|
||||||
main_window.show()
|
main_window.show()
|
||||||
|
|
||||||
|
if args.connect:
|
||||||
|
if args.HOST:
|
||||||
|
main_window.conn_menu.host_set_line.setText(args.HOST)
|
||||||
|
if args.PORT:
|
||||||
|
main_window.conn_menu.port_set_spin.setValue(int(args.PORT))
|
||||||
|
main_window.connect_btn.click()
|
||||||
|
|
||||||
await app_quit_event.wait()
|
await app_quit_event.wait()
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user