thermostat/pytec/model/thermostat.py
atse 872b7e02f3 Make interrupted connection handling more elegant
* Show a disconnected info box informing the user that the device was
  forcefully disconnected and requires user intervention.

* Don't print exception info to console on connection failure to avoid
  cluttering it up with programmer info.
2024-06-28 12:59:53 +08:00

138 lines
4.3 KiB
Python

from pytec.aioclient import Client
from PyQt6.QtCore import pyqtSignal, QObject, pyqtSlot
from qasync import asyncSlot
from model.property import Property, PropertyMeta
import asyncio
import logging
class WrappedClient(QObject, Client):
connection_error = pyqtSignal()
async def _read_line(self):
try:
return await super()._read_line()
except (Exception, TimeoutError, asyncio.exceptions.TimeoutError):
self.connection_error.emit()
class Thermostat(QObject, metaclass=PropertyMeta):
hw_rev = Property(dict)
fan = Property(dict)
thermistor = Property(list)
pid = Property(list)
pwm = Property(list)
postfilter = Property(list)
interval = Property(list)
report = Property(list)
info_box_trigger = pyqtSignal(str, str)
def __init__(self, parent, client, update_s):
self._update_s = update_s
self._client = client
self._watch_task = None
self._report_mode_task = None
self._poll_for_report = True
super().__init__(parent)
async def run(self):
self.task = asyncio.create_task(self.update_params())
while True:
if self.task.done():
if self.task.exception() is not None:
try:
raise self.task.exception()
except (
Exception,
TimeoutError,
asyncio.exceptions.TimeoutError,
):
logging.error(
"Encountered an error while updating parameter tree.",
exc_info=True,
)
_ = self.task.result()
self.task = asyncio.create_task(self.update_params())
await asyncio.sleep(self._update_s)
async def get_hw_rev(self):
self.hw_rev = await self._client.hw_rev()
return self.hw_rev
async def update_params(self):
self.fan = await self._client.get_fan()
self.pwm = await self._client.get_pwm()
if self._poll_for_report:
self.report = await self._client.report()
self.interval = [
self.report[i]["interval"] for i in range(len(self.report))
]
self.pid = await self._client.get_pid()
self.thermistor = await self._client.get_steinhart_hart()
self.postfilter = await self._client.get_postfilter()
def connected(self):
return self._client.connected
def connecting(self):
return self._client.connecting
def start_watching(self):
self._watch_task = asyncio.create_task(self.run())
@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.task.cancel()
self.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)
self.interval = [
self.report[i]["interval"] for i in range(len(self.report))
]
async def disconnect(self):
await self._client.end_session()
async def set_ipv4(self, ipv4):
await self._client.set_param("ipv4", ipv4)
async def get_ipv4(self):
return await self._client.ipv4()
@asyncSlot()
async def save_cfg(self, ch):
await self._client.save_config(ch)
self.info_box_trigger.emit(
"Config loaded", f"Channel {ch} Config has been loaded from flash."
)
@asyncSlot()
async def load_cfg(self, ch):
await self._client.load_config(ch)
self.info_box_trigger.emit(
"Config loaded", f"Channel {ch} Config has been loaded from flash."
)
async def dfu(self):
await self._client.dfu()
async def reset(self):
await self._client.reset()
@pyqtSlot(float)
def set_update_s(self, update_s):
self._update_s = update_s