Compare commits

...

38 Commits

Author SHA1 Message Date
34911e395b Extra bail removed 2024-08-23 18:24:40 +08:00
78e9068e2b return hwrev when start_session 2024-08-23 18:24:40 +08:00
04d073bc18 state str 2024-08-23 18:24:40 +08:00
255f7b7188 params update concurrently 2024-08-23 18:24:40 +08:00
70aee95914 Compact 2024-08-23 18:24:40 +08:00
e3701ece99 Stuff non-UI changes in Thermostat model 2024-08-23 18:24:40 +08:00
26c3d330c4 State dependend UI 2024-08-23 18:24:35 +08:00
671fb4fcc6 State 2024-08-23 18:24:18 +08:00
c0a638187c Actually its OSError 2024-08-23 18:24:18 +08:00
3fb450d2ca _ 2024-08-23 18:24:18 +08:00
f887413d82 conneting 2024-08-23 18:24:18 +08:00
fcadb8aa44 Remove wait_for
OSError raised anyways
2024-08-23 18:24:18 +08:00
16f3e81384 Raise if OSError 2024-08-23 18:24:18 +08:00
baf629ece7 Sep. line 2024-08-23 18:24:17 +08:00
87d09d8c7f {start,end}_session -> [dis]connect 2024-08-23 18:24:17 +08:00
12a3154493 In 2024-08-23 18:24:17 +08:00
f7665ea736 Compacting 2024-08-23 18:24:17 +08:00
16369cec71 try-block 2024-08-23 18:24:17 +08:00
1f90a2e9fe Fix? 2024-08-23 18:24:17 +08:00
022e6b9bda Connecting task moved? 2024-08-23 18:24:17 +08:00
e6b7a482ba AsyncIO version Client -> AsyncioClient 2024-08-23 18:24:17 +08:00
dd0463b85b Exclusively use the Thermostat object as a medium
All calls to the Thermostat should be forwarded by the medium.
2024-08-23 18:24:12 +08:00
d583abba69 Integrate WrappedClient into Thermostat model 2024-08-23 18:23:40 +08:00
9781c75319 Should not stop cancelling read if timeout'd 2024-08-23 18:23:39 +08:00
711bd14ce6 Fix Autotuner state for forceful disconnect 2024-08-23 18:23:39 +08:00
f230a51a52 Correct exception catching
asyncio.Task.result() is simply going to throw the exception in
asyncio.Task.exception(), there is no need to manually throw it.
2024-08-23 18:23:39 +08:00
446d5c4d22 Make connection loss handling more elegant
Show an info box on connection lost informing the user that the
Thermostat was forcefully disconnected.
2024-08-23 18:23:39 +08:00
5694951a23 ================gui_dev-fix_asyncio=============== 2024-08-23 18:23:39 +08:00
bd9da08fc7 This is bail 2024-08-23 18:23:36 +08:00
a087622371 task -> _connecting_task 2024-08-23 18:19:28 +08:00
2b265d6e36 Resgister task 2024-08-23 18:18:57 +08:00
9312433b98 ip -> host 2024-08-23 18:17:35 +08:00
2f56281031 Lazy evaluating for debug string command 2024-08-23 18:17:35 +08:00
61a48c37d1 Add pytec runnables 2024-08-23 18:17:35 +08:00
06e52a627f PYTHON shell 2024-08-23 18:17:35 +08:00
10acb23c82 Exactlier wording 2024-08-23 18:17:33 +08:00
159fdd863b unused 2024-08-23 18:13:58 +08:00
607d0f80e4 encoding 2024-08-23 18:13:58 +08:00
8 changed files with 195 additions and 160 deletions

View File

@ -118,6 +118,20 @@
]; ];
}; };
pytec-dev-wrappers = pkgs.runCommandNoCC "pytec-dev-wrappers" {}
''
mkdir -p $out/bin
for program in ${self}/pytec/*.py; do
if [ -x $program ]; then
progname=`basename -s .py $program`
outname=$out/bin/$progname
echo "#!${pkgs.bash}/bin/bash" >> $outname
echo "exec python3 -m pytec.$progname \"\$@\"" >> $outname
chmod 755 $outname
fi
done
'';
thermostat_gui = pkgs.python3Packages.buildPythonPackage { thermostat_gui = pkgs.python3Packages.buildPythonPackage {
pname = "thermostat_gui"; pname = "thermostat_gui";
version = "0.0.0"; version = "0.0.0";
@ -163,6 +177,7 @@
rust rust
openocd openocd
dfu-util dfu-util
pytec-dev-wrappers
] ]
++ (with python3Packages; [ ++ (with python3Packages; [
numpy numpy
@ -174,6 +189,9 @@
pglive pglive
qtextras qtextras
]); ]);
shellHook = ''
export PYTHONPATH=`pwd`/pytec:$PYTHONPATH
'';
}; };
defaultPackage.x86_64-linux = thermostat; defaultPackage.x86_64-linux = thermostat;
}; };

0
pytec/autotune.py Normal file → Executable file
View File

View File

@ -1,10 +1,10 @@
import asyncio import asyncio
from pytec.aioclient import Client from pytec.aioclient import AsyncioClient
async def main(): async def main():
tec = Client() tec = AsyncioClient()
await tec.start_session() # (host="192.168.1.26", port=23) await tec.connect() # (host="192.168.1.26", port=23)
await tec.set_param("s-h", 1, "t0", 20) await tec.set_param("s-h", 1, "t0", 20)
print(await tec.get_pwm()) print(await tec.get_pwm())
print(await tec.get_pid()) print(await tec.get_pid())

0
pytec/plot.py Normal file → Executable file
View File

View File

@ -7,56 +7,30 @@ class CommandError(Exception):
pass pass
class StoppedConnecting(Exception): class AsyncioClient:
pass
class Client:
def __init__(self): def __init__(self):
self._reader = None self._reader = None
self._writer = None self._writer = None
self._connecting_task = None
self._command_lock = asyncio.Lock() self._command_lock = asyncio.Lock()
self._report_mode_on = False self._report_mode_on = False
self.timeout = None self.timeout = None
async def start_session(self, host="192.168.1.26", port=23, timeout=None): async def connect(self, host="192.168.1.26", port=23):
"""Start session to Thermostat at specified host and port. """Connect to Thermostat at specified host and port.
Throws StoppedConnecting if disconnect was called while connecting.
Throws asyncio.TimeoutError if timeout was exceeded.
Example:: Example::
client = Client() client = AsyncioClient()
try: await client.connect()
await client.start_session()
except StoppedConnecting:
print("Stopped connecting")
""" """
self._connecting_task = asyncio.create_task( self._reader, self._writer = await asyncio.open_connection(host, port)
asyncio.wait_for(asyncio.open_connection(host, port), timeout)
)
self.timeout = timeout
try:
self._reader, self._writer = await self._connecting_task
except asyncio.CancelledError:
raise StoppedConnecting
finally:
self._connecting_task = None
await self._check_zero_limits() await self._check_zero_limits()
def connecting(self):
"""Returns True if client is connecting"""
return self._connecting_task is not None
def connected(self): def connected(self):
"""Returns True if client is connected""" """Returns True if client is connected"""
return self._writer is not None return self._writer is not None
async def end_session(self): async def disconnect(self):
"""End session to Thermostat if connected, cancel connection if connecting""" """Disconnect from the Thermostat"""
if self._connecting_task is not None:
self._connecting_task.cancel()
if self._writer is None: if self._writer is None:
return return
@ -93,11 +67,10 @@ class Client:
async def _command(self, *command): async def _command(self, *command):
async with self._command_lock: async with self._command_lock:
# protect the read-write process from being cancelled midway line = await self._read_write(command)
line = await asyncio.shield(self._read_write(command))
response = json.loads(line) response = json.loads(line)
logging.debug(f"{command}: {response}") logging.debug("%s: %s", command, response)
if "error" in response: if "error" in response:
raise CommandError(response["error"]) raise CommandError(response["error"])
return response return response
@ -268,7 +241,7 @@ class Client:
self._writer.write("reset\n".encode("utf-8")) self._writer.write("reset\n".encode("utf-8"))
await self._writer.drain() await self._writer.drain()
await self.end_session() await self.disconnect()
async def dfu(self): async def dfu(self):
"""Put the Thermostat in DFU update mode """Put the Thermostat in DFU update mode
@ -281,7 +254,7 @@ class Client:
self._writer.write("dfu\n".encode("utf-8")) self._writer.write("dfu\n".encode("utf-8"))
await self._writer.drain() await self._writer.drain()
await self.end_session() await self.disconnect()
async def ipv4(self): async def ipv4(self):
"""Get the IPv4 settings of the Thermostat""" """Get the IPv4 settings of the Thermostat"""

View File

@ -4,10 +4,10 @@ from autotune import PIDAutotuneState, PIDAutotune
class PIDAutoTuner(QObject): class PIDAutoTuner(QObject):
def __init__(self, parent, client, num_of_channel): def __init__(self, parent, thermostat, num_of_channel):
super().__init__(parent) super().__init__(parent)
self._client = client self._thermostat = thermostat
self.autotuners = [PIDAutotune(25) for _ in range(num_of_channel)] self.autotuners = [PIDAutotune(25) for _ in range(num_of_channel)]
self.target_temp = [20.0 for _ in range(num_of_channel)] self.target_temp = [20.0 for _ in range(num_of_channel)]
self.test_current = [1.0 for _ in range(num_of_channel)] self.test_current = [1.0 for _ in range(num_of_channel)]
@ -37,7 +37,7 @@ class PIDAutoTuner(QObject):
async def stop_pid_from_running(self, ch): async def stop_pid_from_running(self, ch):
self.autotuners[ch].setOff() self.autotuners[ch].setOff()
await self._client.set_param("pwm", ch, "i_set", 0) await self._thermostat.set_param("pwm", ch, "i_set", 0)
@asyncSlot(list) @asyncSlot(list)
async def tick(self, report): async def tick(self, report):
@ -56,21 +56,21 @@ class PIDAutoTuner(QObject):
self.autotuners[ch].run( self.autotuners[ch].run(
channel_report["temperature"], channel_report["time"] channel_report["temperature"], channel_report["time"]
) )
await self._client.set_param( await self._thermostat.set_param(
"pwm", ch, "i_set", self.autotuners[ch].output() "pwm", ch, "i_set", self.autotuners[ch].output()
) )
case PIDAutotuneState.STATE_SUCCEEDED: case PIDAutotuneState.STATE_SUCCEEDED:
kp, ki, kd = self.autotuners[ch].get_tec_pid() kp, ki, kd = self.autotuners[ch].get_tec_pid()
self.autotuners[ch].setOff() self.autotuners[ch].setOff()
await self._client.set_param("pid", ch, "kp", kp) await self._thermostat.set_param("pid", ch, "kp", kp)
await self._client.set_param("pid", ch, "ki", ki) await self._thermostat.set_param("pid", ch, "ki", ki)
await self._client.set_param("pid", ch, "kd", kd) await self._thermostat.set_param("pid", ch, "kd", kd)
await self._client.set_param("pwm", ch, "pid") await self._thermostat.set_param("pwm", ch, "pid")
await self._client.set_param( await self._thermostat.set_param(
"pid", ch, "target", self.target_temp[ch] "pid", ch, "target", self.target_temp[ch]
) )
case PIDAutotuneState.STATE_FAILED: case PIDAutotuneState.STATE_FAILED:
self.autotuners[ch].setOff() self.autotuners[ch].setOff()
await self._client.set_param("pwm", ch, "i_set", 0) await self._thermostat.set_param("pwm", ch, "i_set", 0)

View File

@ -1,20 +1,16 @@
from pytec.aioclient import Client
from PyQt6.QtCore import pyqtSignal, QObject, pyqtSlot from PyQt6.QtCore import pyqtSignal, QObject, pyqtSlot
from qasync import asyncSlot from qasync import asyncSlot
from pytec.gui.model.property import Property, PropertyMeta from pytec.gui.model.property import Property, PropertyMeta
import asyncio import asyncio
import logging import logging
from enum import Enum
from pytec.aioclient import AsyncioClient
class WrappedClient(QObject, Client): class ThermostatConnectionState(Enum):
connection_error = pyqtSignal() DISCONNECTED = "disconnected"
CONNECTING = "connecting"
async def _read_line(self): CONNECTED = "connected"
try:
return await super()._read_line()
except (Exception, TimeoutError, asyncio.exceptions.TimeoutError):
logging.error("Client connection error, disconnecting", exc_info=True)
self.connection_error.emit()
class Thermostat(QObject, metaclass=PropertyMeta): class Thermostat(QObject, metaclass=PropertyMeta):
@ -27,53 +23,70 @@ class Thermostat(QObject, metaclass=PropertyMeta):
interval = Property(list) interval = Property(list)
report = Property(list) report = Property(list)
info_box_trigger = pyqtSignal(str, str) info_box_trigger = pyqtSignal(str, str)
connection_error = pyqtSignal()
def __init__(self, parent, client, update_s): def __init__(self, parent, update_s):
self._update_s = update_s self._update_s = update_s
self._client = client self._client = AsyncioClient()
self._watch_task = None self._watch_task = None
self._report_mode_task = None self._report_mode_task = None
self._poll_for_report = True self._poll_for_report = True
self._update_params_task = None
self.connection_errored = False
super().__init__(parent) super().__init__(parent)
async def start_session(self, host, port):
await self._client.connect(host, port)
hw_rev_data = await self.get_hw_rev()
self.start_watching()
return hw_rev_data
async def run(self): async def run(self):
self.task = asyncio.create_task(self.update_params()) self._update_params_task = asyncio.create_task(self.update_params())
while True: while True:
if self.task.done(): if self._update_params_task.done():
if self.task.exception() is not None:
try: try:
raise self.task.exception() self._update_params_task.result()
except asyncio.TimeoutError: except OSError:
logging.error( logging.error(
"Encountered an error while updating parameter tree.", "Encountered an error while polling for information from Thermostat.",
exc_info=True, exc_info=True,
) )
_ = self.task.result() self.connection_error.emit()
self.task = asyncio.create_task(self.update_params()) return
self._update_params_task = asyncio.create_task(self.update_params())
await asyncio.sleep(self._update_s) await asyncio.sleep(self._update_s)
@pyqtSlot()
def timed_out(self):
self.connection_errored = True
async def get_hw_rev(self): async def get_hw_rev(self):
self.hw_rev = await self._client.hw_rev() self.hw_rev = await self._client.hw_rev()
return self.hw_rev return self.hw_rev
async def update_params(self): async def update_params(self):
self.fan = await self._client.get_fan() fan_task = asyncio.create_task(self._client.get_fan())
self.pwm = await self._client.get_pwm() pwm_task = asyncio.create_task(self._client.get_pwm())
pid_task = asyncio.create_task(self._client.get_pid())
report_task = asyncio.create_task(self._client.report())
thermistor_task = asyncio.create_task(self._client.get_steinhart_hart())
postfilter_task = asyncio.create_task(self._client.get_postfilter())
self.fan = await fan_task
self.pwm = await pwm_task
if self._poll_for_report: if self._poll_for_report:
self.report = await self._client.report() self.report = await report_task
self.interval = [ self.interval = [
self.report[i]["interval"] for i in range(len(self.report)) self.report[i]["interval"] for i in range(len(self.report))
] ]
self.pid = await self._client.get_pid() self.pid = await pid_task
self.thermistor = await self._client.get_steinhart_hart() self.thermistor = await thermistor_task
self.postfilter = await self._client.get_postfilter() self.postfilter = await postfilter_task
def connected(self): def connected(self):
return self._client.connected() return self._client.connected()
def connecting(self):
return self._client.connecting()
def start_watching(self): def start_watching(self):
self._watch_task = asyncio.create_task(self.run()) self._watch_task = asyncio.create_task(self.run())
@ -83,8 +96,8 @@ class Thermostat(QObject, metaclass=PropertyMeta):
await self.set_report_mode(False) await self.set_report_mode(False)
self._watch_task.cancel() self._watch_task.cancel()
self._watch_task = None self._watch_task = None
self.task.cancel() self._update_params_task.cancel()
self.task = None self._update_params_task = None
async def set_report_mode(self, enabled: bool): async def set_report_mode(self, enabled: bool):
self._poll_for_report = not enabled self._poll_for_report = not enabled
@ -101,7 +114,10 @@ class Thermostat(QObject, metaclass=PropertyMeta):
] ]
async def end_session(self): async def end_session(self):
await self._client.end_session() await self.set_report_mode(False)
self.stop_watching()
await self._client.disconnect()
self.connection_errored = False
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)
@ -132,3 +148,12 @@ class Thermostat(QObject, metaclass=PropertyMeta):
@pyqtSlot(float) @pyqtSlot(float)
def set_update_s(self, update_s): def set_update_s(self, update_s):
self._update_s = update_s self._update_s = update_s
async def set_fan(self, power="auto"):
await self._client.set_fan(power)
async def get_fan(self):
return await self._client.get_fan()
async def set_param(self, topic, channel, field="", value=""):
await self._client.set_param(topic, channel, field, value)

139
pytec/tec_qt.py Normal file → Executable file
View File

@ -7,12 +7,11 @@ 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.pid_autotuner import PIDAutoTuner
from pytec.gui.model.thermostat import WrappedClient, Thermostat from pytec.gui.model.thermostat import Thermostat, ThermostatConnectionState
import json import json
from autotune import PIDAutotuneState from autotune import PIDAutotuneState
from qasync import asyncSlot, asyncClose from qasync import asyncSlot, asyncClose
import qasync import qasync
from pytec.aioclient import StoppedConnecting
import asyncio import asyncio
import logging import logging
import argparse import argparse
@ -30,9 +29,9 @@ def get_argparser():
"--connect", "--connect",
default=None, default=None,
action="store_true", action="store_true",
help="Automatically connect to the specified Thermostat in IP:port format", help="Automatically connect to the specified Thermostat in host:port format",
) )
parser.add_argument("IP", metavar="ip", default=None, nargs="?") parser.add_argument("HOST", metavar="host", default=None, nargs="?")
parser.add_argument("PORT", metavar="port", default=None, nargs="?") parser.add_argument("PORT", metavar="port", default=None, nargs="?")
parser.add_argument( parser.add_argument(
"-l", "-l",
@ -63,17 +62,25 @@ class MainWindow(QtWidgets.QMainWindow):
self.hw_rev_data = None self.hw_rev_data = None
self.info_box = InfoBox() self.info_box = InfoBox()
self.client = WrappedClient(self)
self.client.connection_error.connect(self.bail)
self.thermostat = Thermostat( self.thermostat = Thermostat(
self, self.client, self.report_refresh_spin.value() self, self.report_refresh_spin.value()
)
self._connecting_task = None
def handle_connection_error():
self.info_box.display_info_box(
"Connection Error", "Thermostat connection lost. Is it unplugged?"
) )
self.autotuners = PIDAutoTuner(self, self.client, 2) self.thermostat.connection_error.connect(handle_connection_error)
self.thermostat.connection_error.connect(self.thermostat.timed_out)
self.thermostat.connection_error.connect(self.bail)
self.autotuners = PIDAutoTuner(self, self.thermostat, 2)
def get_ctrl_panel_config(args): def get_ctrl_panel_config(args):
with open(args.param_tree, "r") 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"]
param_tree_sigActivated_handles = [ param_tree_sigActivated_handles = [
@ -167,20 +174,33 @@ class MainWindow(QtWidgets.QMainWindow):
self.channel_graphs.clear_graphs() self.channel_graphs.clear_graphs()
async def _on_connection_changed(self, result): async def _on_connection_changed(self, result):
self.graph_group.setEnabled(result) match result:
self.report_group.setEnabled(result) case ThermostatConnectionState.CONNECTED:
self.thermostat_settings.setEnabled(result) self.graph_group.setEnabled(True)
self.report_group.setEnabled(True)
self.thermostat_settings.setEnabled(True)
self.conn_menu.host_set_line.setEnabled(not result) self.conn_menu.host_set_line.setEnabled(False)
self.conn_menu.port_set_spin.setEnabled(not result) self.conn_menu.port_set_spin.setEnabled(False)
self.connect_btn.setText("Disconnect" if result else "Connect") self.connect_btn.setText("Disconnect")
if result:
self.hw_rev_data = await self.thermostat.get_hw_rev()
logging.debug(self.hw_rev_data)
self._status(self.hw_rev_data) self._status(self.hw_rev_data)
self.thermostat.start_watching()
else: 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)
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.status_lbl.setText("Disconnected")
self.background_task_lbl.setText("Ready.") self.background_task_lbl.setText("Ready.")
self.loading_spinner.hide() self.loading_spinner.hide()
@ -189,15 +209,15 @@ class MainWindow(QtWidgets.QMainWindow):
self.thermostat_ctrl_menu.fan_pwm_warning.setToolTip("") self.thermostat_ctrl_menu.fan_pwm_warning.setToolTip("")
self.clear_graphs() self.clear_graphs()
self.report_box.setChecked(False) self.report_box.setChecked(False)
if not Thermostat.connecting or Thermostat.connected:
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:
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.autotuners.stop_pid_from_running(ch)
await self.thermostat.set_report_mode(False)
self.thermostat.stop_watching()
def _status(self, hw_rev_d: dict): def _status(self, hw_rev_d: dict):
logging.debug(hw_rev_d)
self.status_lbl.setText( self.status_lbl.setText(
f"Connected to Thermostat v{hw_rev_d['rev']['major']}.{hw_rev_d['rev']['minor']}" f"Connected to Thermostat v{hw_rev_d['rev']['major']}.{hw_rev_d['rev']['minor']}"
) )
@ -221,7 +241,7 @@ class MainWindow(QtWidgets.QMainWindow):
await self.thermostat.set_report_mode(enabled) await self.thermostat.set_report_mode(enabled)
@asyncClose @asyncClose
async def closeEvent(self, event): async def closeEvent(self, _event):
try: try:
await self.bail() await self.bail()
except: except:
@ -229,36 +249,36 @@ class MainWindow(QtWidgets.QMainWindow):
@asyncSlot() @asyncSlot()
async def on_connect_btn_clicked(self): async def on_connect_btn_clicked(self):
host, port = ( if (self._connecting_task is None) and (not self.thermostat.connected()):
self.conn_menu.host_set_line.text(), await self._on_connection_changed(ThermostatConnectionState.CONNECTING)
self.conn_menu.port_set_spin.value(),
self._connecting_task = asyncio.create_task(
self.thermostat.start_session(
host=self.conn_menu.host_set_line.text(),
port=self.conn_menu.port_set_spin.value(),
)
) )
try: try:
if not (self.client.connecting() or self.client.connected()): self.hw_rev_data = await self._connecting_task
self.status_lbl.setText("Connecting...") except (OSError, asyncio.CancelledError) as exc:
self.connect_btn.setText("Stop") await self.bail()
self.conn_menu.host_set_line.setEnabled(False) if isinstance(exc, asyncio.CancelledError):
self.conn_menu.port_set_spin.setEnabled(False)
try:
await self.client.start_session(host=host, port=port, timeout=5)
except StoppedConnecting:
return return
await self._on_connection_changed(True) raise
finally:
self._connecting_task = None
await self._on_connection_changed(ThermostatConnectionState.CONNECTED)
elif self._connecting_task is not None:
self._connecting_task.cancel()
else: else:
await self.bail() await self.bail()
# TODO: Remove asyncio.TimeoutError in Python 3.11
except (OSError, asyncio.TimeoutError):
try:
await self.bail()
except ConnectionResetError:
pass
@asyncSlot() @asyncSlot()
async def bail(self): async def bail(self):
await self._on_connection_changed(False) await self._on_connection_changed(ThermostatConnectionState.DISCONNECTED)
await self.client.end_session() await self.thermostat.end_session()
@asyncSlot(object, object) @asyncSlot(object, object)
async def send_command(self, param, changes): async def send_command(self, param, changes):
@ -280,7 +300,7 @@ class MainWindow(QtWidgets.QMainWindow):
else: else:
set_param_args = (*thermostat_param, data) set_param_args = (*thermostat_param, data)
param.child(*param.childPath(inner_param)).setOpts(lock=True) param.child(*param.childPath(inner_param)).setOpts(lock=True)
await self.client.set_param(*set_param_args) await self.thermostat.set_param(*set_param_args)
param.child(*param.childPath(inner_param)).setOpts(lock=False) param.child(*param.childPath(inner_param)).setOpts(lock=False)
if inner_param.opts.get("pid_autotune", None) is not None: if inner_param.opts.get("pid_autotune", None) is not None:
@ -296,7 +316,7 @@ class MainWindow(QtWidgets.QMainWindow):
if activater is not None: if activater is not None:
if activater[1] == "ch": if activater[1] == "ch":
activater[1] = ch activater[1] = ch
await self.client.set_param(*activater) await self.thermostat.set_param(*activater)
@asyncSlot() @asyncSlot()
async def pid_auto_tune_request(self, ch=0): async def pid_auto_tune_request(self, ch=0):
@ -359,24 +379,24 @@ class MainWindow(QtWidgets.QMainWindow):
@asyncSlot(int) @asyncSlot(int)
async def fan_set_request(self, value): async def fan_set_request(self, value):
assert self.client.connected() assert self.thermostat.connected()
if self.thermostat_ctrl_menu.fan_auto_box.isChecked(): if self.thermostat_ctrl_menu.fan_auto_box.isChecked():
with QSignalBlocker(self.thermostat_ctrl_menu.fan_auto_box): with QSignalBlocker(self.thermostat_ctrl_menu.fan_auto_box):
self.thermostat_ctrl_menu.fan_auto_box.setChecked(False) self.thermostat_ctrl_menu.fan_auto_box.setChecked(False)
await self.client.set_fan(value) await self.thermostat.set_fan(value)
if not self.hw_rev_data["settings"]["fan_pwm_recommended"]: if not self.hw_rev_data["settings"]["fan_pwm_recommended"]:
self.thermostat_ctrl_menu.set_fan_pwm_warning() self.thermostat_ctrl_menu.set_fan_pwm_warning()
@asyncSlot(int) @asyncSlot(int)
async def fan_auto_set_request(self, enabled): async def fan_auto_set_request(self, enabled):
assert self.client.connected() assert self.thermostat.connected()
if enabled: if enabled:
await self.client.set_fan("auto") await self.thermostat.set_fan("auto")
self.fan_update(await self.client.get_fan()) self.fan_update(await self.thermostat.get_fan())
else: else:
await self.client.set_fan( await self.thermostat.set_fan(
self.thermostat_ctrl_menu.fan_power_slider.value() self.thermostat_ctrl_menu.fan_power_slider.value()
) )
@ -396,14 +416,14 @@ class MainWindow(QtWidgets.QMainWindow):
async def dfu_request(self, _): async def dfu_request(self, _):
assert self.thermostat.connected() assert self.thermostat.connected()
await self._on_connection_changed(False) await self._on_connection_changed(ThermostatConnectionState.DISCONNECTED)
await self.thermostat.dfu() await self.thermostat.dfu()
@asyncSlot(bool) @asyncSlot(bool)
async def reset_request(self, _): async def reset_request(self, _):
assert self.thermostat.connected() assert self.thermostat.connected()
await self._on_connection_changed(False) await self._on_connection_changed(ThermostatConnectionState.DISCONNECTED)
await self.thermostat.reset() await self.thermostat.reset()
await asyncio.sleep(0.1) # Wait for the reset to start await asyncio.sleep(0.1) # Wait for the reset to start
@ -422,8 +442,7 @@ class MainWindow(QtWidgets.QMainWindow):
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._client.end_session() await self.bail()
await self._on_connection_changed(False)
async def coro_main(): async def coro_main():