gui: update for the new driver code

- Default to use active report mode
- Connection will be retried upon abnormal disconnection
- Remove poll every _s and apply btn
This commit is contained in:
linuswck 2024-08-05 15:23:54 +08:00
parent 82c46e04d0
commit 572e2dbc5d
3 changed files with 173 additions and 350 deletions

View File

@ -152,12 +152,6 @@ class InvalidDataType(Exception):
class InvalidCmd(Exception): class InvalidCmd(Exception):
pass pass
class NoAckRecv(Exception):
pass
class StoppedConnecting(Exception):
pass
class Device: class Device:
def __init__(self, send_cmd_handler, send_raw_cmd_handler, read_msg_queue): def __init__(self, send_cmd_handler, send_raw_cmd_handler, read_msg_queue):
self._cmd = CmdList.device self._cmd = CmdList.device

View File

@ -18,9 +18,9 @@ import os
import argparse import argparse
import logging import logging
import asyncio import asyncio
from driver.kirdy_async import Kirdy, StoppedConnecting from driver.kirdy import Kirdy as Kirdy_Driver
import qasync import qasync
from qasync import asyncSlot, asyncClose from qasync import asyncClose
from collections import deque from collections import deque
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
from time import time from time import time
@ -68,77 +68,65 @@ def siConvert(val, suffix, typ=float):
else: else:
return v return v
class KirdyDataWatcher(QObject): class Kirdy(QObject):
""" connected_sig = pyqtSignal(bool)
This class provides various signals for Mainwindow to update various kinds of GUI objects
"""
connection_error_sig = pyqtSignal()
setting_update_sig = pyqtSignal(dict) setting_update_sig = pyqtSignal(dict)
report_update_sig = pyqtSignal(dict) report_update_sig = pyqtSignal(dict)
def __init__(self, parent, kirdy, update_s): def __init__(self, parent, kirdy, _poll_interval):
self._update_s = update_s
self._kirdy = kirdy
self._watch_task = None
self._report_mode_task = None
self._poll_for_report = True
super().__init__(parent) super().__init__(parent)
self._poll_interval = _poll_interval
self._kirdy = kirdy
self._kirdy.set_connected_sig(self.connected_sig)
self.connected_sig.connect(self.start_polling)
self.connected_sig.connect(self.connected_setup)
async def signal_emitter(self): self._kirdy.set_report_sig(self.report_update_sig)
settings_summary = await self._kirdy.device.get_settings_summary() self._timer = QtCore.QBasicTimer()
self.setting_update_sig.emit(settings_summary)
if self._poll_for_report:
status_report = await self._kirdy.device.get_status_report()
self.report_update_sig.emit(status_report)
async def run(self): def connected(self):
try: return self._kirdy.connected()
task = asyncio.create_task(self.signal_emitter())
while True:
if task.done():
_ = task.result()
task = asyncio.create_task(self.signal_emitter())
await asyncio.sleep(self._update_s)
except asyncio.CancelledError:
task.cancel()
except Exception as e:
logging.error(COMMON_ERROR_MSG)
self._kirdy.stop_report_mode()
self.connection_error_sig.emit()
def start_watching(self): def connecting(self):
self._watch_task = asyncio.create_task(self.run()) return self._kirdy.connecting()
async def stop_watching(self): def start_session(self, host, port):
if self._watch_task is not None: self._kirdy.start_session(host=host, port=port)
self._watch_task.cancel()
await self._watch_task
self._watch_task = None
await self.set_report_mode(False)
async def set_report_mode(self, enabled: bool): def end_session(self):
self._poll_for_report = not enabled if self._timer.isActive():
if enabled: self._timer.stop()
self._report_mode_task = asyncio.create_task(self.report_mode()) self._kirdy.end_session()
@pyqtSlot(bool)
def connected_setup(self, connected):
if connected:
self._kirdy.device.set_active_report_mode(True)
def timerEvent(self, event):
self._kirdy.device.get_settings_summary(sig=self.setting_update_sig)
@pyqtSlot(bool)
def start_polling(self, start):
if start:
if not(self._timer.isActive()):
self._timer.start(int(self._poll_interval*1000), self)
else:
logging.debug("Kirdy Polling Timer has been started already.")
else: else:
if self._report_mode_task is not None: self._timer.stop()
self._kirdy.stop_report_mode()
await self._report_mode_task
self._report_mode_task = None
async def report_mode(self):
try:
async for status_report in self._kirdy.report_mode():
if status_report["msg_type"] == "Exception":
raise TimeoutError("Connection Timeout")
self.report_update_sig.emit(status_report)
except Exception as e:
logging.error(f"{COMMON_ERROR_MSG}")
self.connection_error_sig.emit()
@pyqtSlot(float) @pyqtSlot(float)
def set_update_s(self, update_s): def set_update_s(self, interval):
self._update_s = update_s self._poll_interval = interval
self.update_polling_rate()
def update_polling_rate(self):
if self._timer.isActive():
self._timer.stop()
self.start_polling()
else:
logging.debug("Attempt to update polling timer when it is stopped")
class Graphs: class Graphs:
def __init__(self, ld_i_set_graph, pd_mon_pwr_graph, tec_i_graph, tec_temp_graph, max_samples=1000): def __init__(self, ld_i_set_graph, pd_mon_pwr_graph, tec_i_graph, tec_temp_graph, max_samples=1000):
@ -152,7 +140,7 @@ class Graphs:
self._tec_i_target_plot = LiveLinePlot(name="Target", pen=pg.mkPen('r')) self._tec_i_target_plot = LiveLinePlot(name="Target", pen=pg.mkPen('r'))
self._tec_i_measure_plot = LiveLinePlot(name="Measure", pen=pg.mkPen('g')) self._tec_i_measure_plot = LiveLinePlot(name="Measure", pen=pg.mkPen('g'))
self._temp_setpoint_line = tec_temp_graph.getPlotItem().addLine(label='{value} °C', pen=pg.mkPen('g')) self._temp_setpoint_line = tec_temp_graph.getPlotItem().addLine(label='{value} ', pen=pg.mkPen('g'))
# Render the temperature setpoint line on top of the temperature being plotted # Render the temperature setpoint line on top of the temperature being plotted
self._temp_setpoint_line.setZValue(10) self._temp_setpoint_line.setZValue(10)
self._temp_setpoint_line.setVisible(False) self._temp_setpoint_line.setVisible(False)
@ -192,7 +180,7 @@ class Graphs:
self.pd_mon_pwr_connector = DataConnector(self._pd_mon_pwr_plot, max_points=self.max_samples) self.pd_mon_pwr_connector = DataConnector(self._pd_mon_pwr_plot, max_points=self.max_samples)
self.connectors += [self.pd_mon_pwr_connector] self.connectors += [self.pd_mon_pwr_connector]
tec_temp_axis = LiveAxis('left', text="Temperature", units="°C") tec_temp_axis = LiveAxis('left', text="Temperature", units="")
tec_temp_axis.showLabel() tec_temp_axis.showLabel()
tec_temp_graph.setAxisItems({'left': tec_temp_axis}) tec_temp_graph.setAxisItems({'left': tec_temp_axis})
tec_temp_graph.addItem(self._tec_setpoint_plot) tec_temp_graph.addItem(self._tec_setpoint_plot)
@ -271,7 +259,7 @@ class Graphs:
# PyQtGraph normally does not update this text when the line # PyQtGraph normally does not update this text when the line
# is not visible, so make sure that the temperature label # is not visible, so make sure that the temperature label
# gets updated always, and doesn't stay at an old value. # gets updated always, and doesn't stay at an old value.
self._temp_setpoint_line.label.setText(f"{temp} °C", color='g') self._temp_setpoint_line.label.setText(f"{temp} ", color='g')
class MutexParameter(pTypes.ListParameter): class MutexParameter(pTypes.ListParameter):
""" """
@ -386,7 +374,7 @@ class MainWindow(QtWidgets.QMainWindow):
THERMOSTAT_PARAMETERS = [ THERMOSTAT_PARAMETERS = [
{'name': 'Readings', 'expanded': True, 'type': 'group', 'children': [ {'name': 'Readings', 'expanded': True, 'type': 'group', 'children': [
{'name': 'Temperature', 'type': 'float', 'format': '{value:.4f} °C', 'readonly': True}, {'name': 'Temperature', 'type': 'float', 'format': '{value:.4f} ', 'readonly': True},
{'name': 'Current through TEC', 'type': 'float', 'suffix': 'A', 'siPrefix': True, 'decimals': 6, 'readonly': True}, {'name': 'Current through TEC', 'type': 'float', 'suffix': 'A', 'siPrefix': True, 'decimals': 6, 'readonly': True},
]}, ]},
{'name': 'Output Config', 'expanded': True, 'type': 'group', 'children': [ {'name': 'Output Config', 'expanded': True, 'type': 'group', 'children': [
@ -395,7 +383,7 @@ class MainWindow(QtWidgets.QMainWindow):
{'name': 'Set Current', 'type': 'float', 'value': 0, 'step': 1, 'limits': (-1000, 1000), 'triggerOnShow': True, 'decimals': 6, {'name': 'Set Current', 'type': 'float', 'value': 0, 'step': 1, 'limits': (-1000, 1000), 'triggerOnShow': True, 'decimals': 6,
'unit': 'mA', 'lock': False, 'target': 'thermostat', 'action': 'set_tec_i_out'}, 'unit': 'mA', 'lock': False, 'target': 'thermostat', 'action': 'set_tec_i_out'},
{'name': 'Set Temperature', 'type': 'float', 'value': 25, 'step': 0.0001, 'limits': (-273, 300), 'format': '{value:.4f}', {'name': 'Set Temperature', 'type': 'float', 'value': 25, 'step': 0.0001, 'limits': (-273, 300), 'format': '{value:.4f}',
'unit': '°C', 'lock': False, 'target': 'thermostat', 'action': 'set_temperature_setpoint'}, 'unit': '', 'lock': False, 'target': 'thermostat', 'action': 'set_temperature_setpoint'},
]}, ]},
{'name': 'Limits', 'expanded': False, 'type': 'group', 'children': [ {'name': 'Limits', 'expanded': False, 'type': 'group', 'children': [
{'name': 'Max Cooling Current', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (0, 1000), {'name': 'Max Cooling Current', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (0, 1000),
@ -410,22 +398,22 @@ class MainWindow(QtWidgets.QMainWindow):
# TODO Temperature ADC Filter Settings # TODO Temperature ADC Filter Settings
{'name': 'Temperature Monitor Config', 'expanded': False, 'type': 'group', 'children': [ {'name': 'Temperature Monitor Config', 'expanded': False, 'type': 'group', 'children': [
{'name': 'Upper Limit', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (-273, 300), {'name': 'Upper Limit', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (-273, 300),
'unit': '°C', 'lock': False, 'target': 'thermostat', 'action': 'set_temp_mon_upper_limit'}, 'unit': '', 'lock': False, 'target': 'thermostat', 'action': 'set_temp_mon_upper_limit'},
{'name': 'Lower Limit', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (-273, 300), {'name': 'Lower Limit', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (-273, 300),
'unit': '°C', 'lock': False, 'target': 'thermostat', 'action': 'set_temp_mon_lower_limit'}, 'unit': '', 'lock': False, 'target': 'thermostat', 'action': 'set_temp_mon_lower_limit'},
]}, ]},
{'name': 'Thermistor Settings','expanded': False, 'type': 'group', 'children': [ {'name': 'Thermistor Settings','expanded': False, 'type': 'group', 'children': [
{'name': 'T₀', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (-273, 300), {'name': 'T₀', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (-273, 300),
'unit': '°C', 'lock': False, 'target': 'thermostat', 'action': 'set_sh_t0'}, 'unit': '', 'lock': False, 'target': 'thermostat', 'action': 'set_sh_t0'},
{'name': 'R₀', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, {'name': 'R₀', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6,
'unit': '', 'lock': False, 'target': 'thermostat', 'action': 'set_sh_r0'}, 'unit': '', 'lock': False, 'target': 'thermostat', 'action': 'set_sh_r0'},
{'name': 'B', 'type': 'float', 'value': 3950, 'step': 1, 'decimals': 4, {'name': 'B', 'type': 'float', 'value': 3950, 'step': 1, 'decimals': 4,
'unit': 'K', 'lock': False, 'target': 'thermostat', 'action': 'set_sh_beta'}, 'unit': 'K', 'lock': False, 'target': 'thermostat', 'action': 'set_sh_beta'},
]}, ]},
{'name': 'PID Config', 'expanded': False, 'type': 'group', 'children': [ {'name': 'PID Config', 'expanded': False, 'type': 'group', 'children': [
{'name': 'Kp', 'type': 'float', 'step': 0.1, 'lock': False, 'target': 'thermostat', 'action': 'set_pid_kp'}, {'name': 'Kp', 'type': 'float', 'step': 0.1, 'decimals': 16, 'lock': False, 'target': 'thermostat', 'action': 'set_pid_kp'},
{'name': 'Ki', 'type': 'float', 'step': 0.1, 'unit': 'Hz', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_ki'}, {'name': 'Ki', 'type': 'float', 'step': 0.1, 'decimals': 16, 'unit': 'Hz', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_ki'},
{'name': 'Kd', 'type': 'float', 'step': 0.1, 'unit': 's', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_kd'}, {'name': 'Kd', 'type': 'float', 'step': 0.1, 'decimals': 16, 'unit': 's', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_kd'},
{'name': "PID Output Clamping", 'expanded': True, 'type': 'group', 'children': [ {'name': "PID Output Clamping", 'expanded': True, 'type': 'group', 'children': [
{'name': 'Minimum', 'type': 'float', 'step': 1, 'limits': (-1000, 1000), 'decimals': 6, {'name': 'Minimum', 'type': 'float', 'step': 1, 'limits': (-1000, 1000), 'decimals': 6,
'unit': 'mA', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_output_min'}, 'unit': 'mA', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_output_min'},
@ -433,9 +421,9 @@ class MainWindow(QtWidgets.QMainWindow):
'unit': 'mA', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_output_max'}, 'unit': 'mA', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_output_max'},
]}, ]},
{'name': 'PID Auto Tune', 'expanded': False, 'type': 'group', 'children': [ {'name': 'PID Auto Tune', 'expanded': False, 'type': 'group', 'children': [
{'name': 'Target Temperature', 'type': 'float', 'value': 20.0, 'step': 0.1, 'unit': '°C', 'format': '{value:.4f}'}, {'name': 'Target Temperature', 'type': 'float', 'value': 20.0, 'step': 0.1, 'unit': '', 'format': '{value:.4f}'},
{'name': 'Test Current', 'type': 'float', 'value': 1000, 'decimals': 6, 'step': 100, 'limits': (-3000, 3000), 'unit': 'mA'}, {'name': 'Test Current', 'type': 'float', 'value': 1000, 'decimals': 6, 'step': 100, 'limits': (-1000, 1000), 'unit': 'mA'},
{'name': 'Temperature Swing', 'type': 'float', 'value': 0.0, 'step': 0.001, 'prefix': '±', 'unit': '°C', 'format': '{value:.4f}'}, {'name': 'Temperature Swing', 'type': 'float', 'value': 0.0, 'step': 0.0001, 'prefix': '±', 'unit': '', 'format': '{value:.4f}'},
{'name': 'Lookback', 'type': 'float', 'value': 5.0, 'step': 0.1, 'unit': 's', 'format': '{value:.4f}'}, {'name': 'Lookback', 'type': 'float', 'value': 5.0, 'step': 0.1, 'unit': 's', 'format': '{value:.4f}'},
{'name': 'Run', 'type': 'action', 'tip': 'Run'}, {'name': 'Run', 'type': 'action', 'tip': 'Run'},
]}, ]},
@ -443,7 +431,7 @@ class MainWindow(QtWidgets.QMainWindow):
] ]
def __init__(self, args): def __init__(self, args):
super(MainWindow, self).__init__() super(MainWindow, self).__init__()
self.kirdy = Kirdy() self.kirdy = Kirdy_Driver()
ui_file_path = importlib.resources.files("ui").joinpath("kirdy_qt.ui") ui_file_path = importlib.resources.files("ui").joinpath("kirdy_qt.ui")
uic.loadUi(ui_file_path, self) uic.loadUi(ui_file_path, self)
@ -517,23 +505,19 @@ class MainWindow(QtWidgets.QMainWindow):
self.pd_mon_pwr_graph.setTitle("PD Mon Power") self.pd_mon_pwr_graph.setTitle("PD Mon Power")
self.connect_btn.clicked.connect(self.show_conn_settings_form) self.connect_btn.clicked.connect(self.show_conn_settings_form)
self.report_box.stateChanged.connect(self.on_report_box_stateChanged)
self.kirdy_data_watcher = KirdyDataWatcher(self, self.kirdy, self.report_refresh_spin.value()) self.kirdy_handler = Kirdy(self, self.kirdy, 1.0)
self.kirdy_data_watcher.connection_error_sig.connect(self.bail)
# TODO: Identify the usable range of set_update_s self.kirdy_handler.setting_update_sig.connect(self.update_ld_ctrl_panel_settings)
self.report_apply_btn.clicked.connect( self.kirdy_handler.setting_update_sig.connect(self.update_thermostat_ctrl_panel_settings)
lambda: self.kirdy_data_watcher.set_update_s(self.report_refresh_spin.value()) self.kirdy_handler.report_update_sig.connect(self.update_ld_ctrl_panel_readings)
) self.kirdy_handler.report_update_sig.connect(self.update_thermostat_ctrl_panel_readings)
self.kirdy_data_watcher.setting_update_sig.connect(self.update_ld_ctrl_panel_settings)
self.kirdy_data_watcher.setting_update_sig.connect(self.update_thermostat_ctrl_panel_settings)
self.kirdy_data_watcher.report_update_sig.connect(self.update_ld_ctrl_panel_readings)
self.kirdy_data_watcher.report_update_sig.connect(self.update_thermostat_ctrl_panel_readings)
self.graphs = Graphs(self.ld_i_set_graph, self.pd_mon_pwr_graph, self.tec_i_graph, self.tec_temp_graph, max_samples=self.max_samples) self.graphs = Graphs(self.ld_i_set_graph, self.pd_mon_pwr_graph, self.tec_i_graph, self.tec_temp_graph, max_samples=self.max_samples)
self.kirdy_data_watcher.report_update_sig.connect(self.graphs.plot_append) self.kirdy_handler.report_update_sig.connect(self.graphs.plot_append)
self.loading_spinner.hide() self.loading_spinner.hide()
self.kirdy_handler.connected_sig.connect(self._on_connection_changed)
def setup_menu_bar(self): def setup_menu_bar(self):
@pyqtSlot(bool) @pyqtSlot(bool)
@ -560,20 +544,21 @@ class MainWindow(QtWidgets.QMainWindow):
) )
self.menu_action_about_gui.triggered.connect(about_gui) self.menu_action_about_gui.triggered.connect(about_gui)
@asyncSlot(bool) @pyqtSlot(bool)
async def dfu_mode(_): def dfu_mode(_):
await self.kirdy.device.dfu() self.kirdy.device.dfu()
await self._on_connection_changed(False) self.kirdy_handler.end_session()
self.menu_action_dfu_mode.triggered.connect(dfu_mode) self.menu_action_dfu_mode.triggered.connect(dfu_mode)
@asyncSlot(bool) @pyqtSlot(bool)
async def reset_kirdy(_): def reset_kirdy(_):
await self._on_connection_changed(False, hard_reset=True) self.kirdy.device.hard_reset()
self.kirdy_handler.end_session()
self.menu_action_hard_reset.triggered.connect(reset_kirdy) self.menu_action_hard_reset.triggered.connect(reset_kirdy)
@asyncSlot(bool) @pyqtSlot(bool)
async def save_settings(_): def save_settings(_):
await self.kirdy.device.save_current_settings_to_flash() self.kirdy.device.save_current_settings_to_flash()
saved = QtWidgets.QMessageBox(self) saved = QtWidgets.QMessageBox(self)
saved.setWindowTitle("Config saved") saved.setWindowTitle("Config saved")
saved.setText(f"Laser diode and thermostat configs have been saved into flash.") saved.setText(f"Laser diode and thermostat configs have been saved into flash.")
@ -581,9 +566,9 @@ class MainWindow(QtWidgets.QMainWindow):
saved.show() saved.show()
self.menu_action_save.triggered.connect(save_settings) self.menu_action_save.triggered.connect(save_settings)
@asyncSlot(bool) @pyqtSlot(bool)
async def load_settings(_): def load_settings(_):
await self.kirdy.device.restore_settings_from_flash() self.kirdy.device.restore_settings_from_flash()
loaded = QtWidgets.QMessageBox(self) loaded = QtWidgets.QMessageBox(self)
loaded.setWindowTitle("Config loaded") loaded.setWindowTitle("Config loaded")
loaded.setText(f"Laser Diode and Thermostat configs have been loaded from flash.") loaded.setText(f"Laser Diode and Thermostat configs have been loaded from flash.")
@ -591,8 +576,8 @@ class MainWindow(QtWidgets.QMainWindow):
loaded.show() loaded.show()
self.menu_action_load.triggered.connect(load_settings) self.menu_action_load.triggered.connect(load_settings)
@asyncSlot(bool) @pyqtSlot(bool)
async def show_update_net_settings_form(_): def show_update_net_settings_form(_):
self.update_net_settings_form.retranslateUi(self.update_net_settings_form) self.update_net_settings_form.retranslateUi(self.update_net_settings_form)
self.update_net_settings_form.show() self.update_net_settings_form.show()
self.menu_action_update_net_settings.triggered.connect(show_update_net_settings_form) self.menu_action_update_net_settings.triggered.connect(show_update_net_settings_form)
@ -607,34 +592,34 @@ class MainWindow(QtWidgets.QMainWindow):
self.conn_settings_form.show() self.conn_settings_form.show()
def _set_up_ctrl_btns(self): def _set_up_ctrl_btns(self):
@asyncSlot(bool) @pyqtSlot(bool)
async def ld_pwr_on(_): def ld_pwr_on(_):
await self.kirdy.laser.set_power_on(True) self.kirdy.laser.set_power_on(True)
self.ld_pwr_on_btn.clicked.connect(ld_pwr_on) self.ld_pwr_on_btn.clicked.connect(ld_pwr_on)
@asyncSlot(bool) @pyqtSlot(bool)
async def ld_pwr_off(_): def ld_pwr_off(_):
await self.kirdy.laser.set_power_on(False) self.kirdy.laser.set_power_on(False)
self.ld_pwr_off_btn.clicked.connect(ld_pwr_off) self.ld_pwr_off_btn.clicked.connect(ld_pwr_off)
@asyncSlot(bool) @pyqtSlot(bool)
async def ld_clear_alarm(_): def ld_clear_alarm(_):
await self.kirdy.laser.clear_alarm() self.kirdy.laser.clear_alarm()
self.ld_clear_alarm_btn.clicked.connect(ld_clear_alarm) self.ld_clear_alarm_btn.clicked.connect(ld_clear_alarm)
@asyncSlot(bool) @pyqtSlot(bool)
async def tec_pwr_on(_): def tec_pwr_on(_):
await self.kirdy.thermostat.set_power_on(True) self.kirdy.thermostat.set_power_on(True)
self.tec_pwr_on_btn.clicked.connect(tec_pwr_on) self.tec_pwr_on_btn.clicked.connect(tec_pwr_on)
@asyncSlot(bool) @pyqtSlot(bool)
async def tec_pwr_off(_): def tec_pwr_off(_):
await self.kirdy.thermostat.set_power_on(False) self.kirdy.thermostat.set_power_on(False)
self.tec_pwr_off_btn.clicked.connect(tec_pwr_off) self.tec_pwr_off_btn.clicked.connect(tec_pwr_off)
@asyncSlot(bool) @pyqtSlot(bool)
async def tec_clear_alarm(_): def tec_clear_alarm(_):
await self.kirdy.thermostat.clear_alarm() self.kirdy.thermostat.clear_alarm()
self.tec_clear_alarm_btn.clicked.connect(tec_clear_alarm) self.tec_clear_alarm_btn.clicked.connect(tec_clear_alarm)
def _set_up_plot_menu(self): def _set_up_plot_menu(self):
@ -678,11 +663,11 @@ class MainWindow(QtWidgets.QMainWindow):
tree.setParameters(self.params[3], showTop=False) tree.setParameters(self.params[3], showTop=False)
self.params[3].sigTreeStateChanged.connect(self.send_command) self.params[3].sigTreeStateChanged.connect(self.send_command)
@asyncSlot() @pyqtSlot()
async def autotune(param): def autotune(param):
match self.autotuner.state(): match self.autotuner.state():
case PIDAutotuneState.STATE_OFF: case PIDAutotuneState.STATE_OFF:
settings = await self.kirdy.device.get_settings_summary() settings = self.kirdy.device.get_settings_summary()
self.autotuner.setParam( self.autotuner.setParam(
param.parent().child('Target Temperature').value(), param.parent().child('Target Temperature').value(),
param.parent().child('Test Current').value() / 1000, param.parent().child('Test Current').value() / 1000,
@ -691,37 +676,37 @@ class MainWindow(QtWidgets.QMainWindow):
param.parent().child('Lookback').value()) param.parent().child('Lookback').value())
self.autotuner.setReady() self.autotuner.setReady()
param.setOpts(title="Stop") param.setOpts(title="Stop")
await self.kirdy.thermostat.set_constant_current_control_mode() self.kirdy.thermostat.set_constant_current_control_mode()
self.kirdy_data_watcher.report_update_sig.connect(self.autotune_tick) self.kirdy_handler.report_update_sig.connect(self.autotune_tick)
self.loading_spinner.show() self.loading_spinner.show()
self.loading_spinner.start() self.loading_spinner.start()
self.background_task_lbl.setText("Autotuning") self.background_task_lbl.setText("Autotuning")
case PIDAutotuneState.STATE_READY | PIDAutotuneState.STATE_RELAY_STEP_UP | PIDAutotuneState.STATE_RELAY_STEP_DOWN: case PIDAutotuneState.STATE_READY | PIDAutotuneState.STATE_RELAY_STEP_UP | PIDAutotuneState.STATE_RELAY_STEP_DOWN:
self.autotuner.setOff() self.autotuner.setOff()
param.setOpts(title="Run") param.setOpts(title="Run")
await self.kirdy.thermostat.set_tec_i_out(0.0) self.kirdy.thermostat.set_tec_i_out(0.0)
self.kirdy_data_watcher.report_update_sig.disconnect(self.autotune_tick) self.kirdy_handler.report_update_sig.disconnect(self.autotune_tick)
self.background_task_lbl.setText("Ready.") self.background_task_lbl.setText("Ready.")
self.loading_spinner.stop() self.loading_spinner.stop()
self.loading_spinner.hide() self.loading_spinner.hide()
self.params[3].child('PID Config', 'PID Auto Tune', 'Run').sigActivated.connect(autotune) self.params[3].child('PID Config', 'PID Auto Tune', 'Run').sigActivated.connect(autotune)
@asyncSlot(dict) @pyqtSlot(dict)
async def autotune_tick(self, report): def autotune_tick(self, report):
match self.autotuner.state(): match self.autotuner.state():
case PIDAutotuneState.STATE_READY | PIDAutotuneState.STATE_RELAY_STEP_UP | PIDAutotuneState.STATE_RELAY_STEP_DOWN: case PIDAutotuneState.STATE_READY | PIDAutotuneState.STATE_RELAY_STEP_UP | PIDAutotuneState.STATE_RELAY_STEP_DOWN:
self.autotuner.run(report['thermostat']['temperature'], report['ts']/1000) self.autotuner.run(report['thermostat']['temperature'], report['ts']/1000)
await self.kirdy.thermostat.set_tec_i_out(self.autotuner.output()) self.kirdy.thermostat.set_tec_i_out(self.autotuner.output())
case PIDAutotuneState.STATE_SUCCEEDED: case PIDAutotuneState.STATE_SUCCEEDED:
kp, ki, kd = self.autotuner.get_tec_pid() kp, ki, kd = self.autotuner.get_tec_pid()
self.autotuner.setOff() self.autotuner.setOff()
self.params[3].child('PID Config', 'PID Auto Tune', 'Run').setOpts(title="Run") self.params[3].child('PID Config', 'PID Auto Tune', 'Run').setOpts(title="Run")
await self.kirdy.thermostat.set_pid_kp(kp) self.kirdy.thermostat.set_pid_kp(kp)
await self.kirdy.thermostat.set_pid_ki(ki) self.kirdy.thermostat.set_pid_ki(ki)
await self.kirdy.thermostat.set_pid_kd(kd) self.kirdy.thermostat.set_pid_kd(kd)
await self.kirdy.thermostat.set_pid_control_mode() self.kirdy.thermostat.set_pid_control_mode()
await self.kirdy.thermostat.set_temperature_setpoint(self.params[3].child('PID Config', 'PID Auto Tune', 'Target Temperature').value()) self.kirdy.thermostat.set_temperature_setpoint(self.params[3].child('PID Config', 'PID Auto Tune', 'Target Temperature').value())
self.kirdy_data_watcher.report_update_sig.disconnect(self.autotune_tick) self.kirdy_handler.report_update_sig.disconnect(self.autotune_tick)
self.background_task_lbl.setText("Ready.") self.background_task_lbl.setText("Ready.")
self.loading_spinner.stop() self.loading_spinner.stop()
self.loading_spinner.hide() self.loading_spinner.hide()
@ -732,8 +717,8 @@ class MainWindow(QtWidgets.QMainWindow):
case PIDAutotuneState.STATE_FAILED: case PIDAutotuneState.STATE_FAILED:
self.autotuner.setOff() self.autotuner.setOff()
self.params[3].child('PID Config', 'PID Auto Tune', 'Run').setOpts(title="Run") self.params[3].child('PID Config', 'PID Auto Tune', 'Run').setOpts(title="Run")
await self.kirdy.thermostat.set_tec_i_out(0.0) self.kirdy.thermostat.set_tec_i_out(0.0)
self.kirdy_data_watcher.report_update_sig.disconnect(self.autotune_tick) self.kirdy_handler.report_update_sig.disconnect(self.autotune_tick)
self.background_task_lbl.setText("Ready.") self.background_task_lbl.setText("Ready.")
self.loading_spinner.stop() self.loading_spinner.stop()
self.loading_spinner.hide() self.loading_spinner.hide()
@ -741,7 +726,8 @@ class MainWindow(QtWidgets.QMainWindow):
self.info_box.setText("PID Autotune is failed.") self.info_box.setText("PID Autotune is failed.")
self.info_box.show() self.info_box.show()
async def _on_connection_changed(self, result, hard_reset=False): @pyqtSlot(bool)
def _on_connection_changed(self, result):
def ctrl_panel_setEnable(result): def ctrl_panel_setEnable(result):
self.ld_status.setEnabled(result) self.ld_status.setEnabled(result)
self.ld_tree.setEnabled(result) self.ld_tree.setEnabled(result)
@ -773,50 +759,41 @@ class MainWindow(QtWidgets.QMainWindow):
self.tec_temp_graph.setEnabled(result) self.tec_temp_graph.setEnabled(result)
graph_group_setEnable(result) graph_group_setEnable(result)
self.report_refresh_spin.setEnabled(result)
self.report_group.setEnabled(result) self.report_group.setEnabled(result)
self.report_refresh_spin.setEnabled(result)
self.report_box.setEnabled(result)
self.report_apply_btn.setEnabled(result)
# TODO: Use QStateMachine to manage connections # TODO: Use QStateMachine to manage connections
self.connect_btn.clicked.disconnect() self.connect_btn.clicked.disconnect()
if result: if result:
self.connect_btn.setText("Disconnect") self.connect_btn.setText("Disconnect")
self.connect_btn.clicked.connect(self.bail) self.connect_btn.clicked.connect(self.kirdy_handler.end_session)
else: # TODO: self.hw_rev_data = self.kirdy.hw_rev()
self.connect_btn.setText("Connect")
self.connect_btn.clicked.connect(self.show_conn_settings_form)
if result:
# TODO: self.hw_rev_data = await self.kirdy.hw_rev()
self._status() self._status()
self.kirdy_data_watcher.start_watching()
else: else:
self.clear_graphs() if self.kirdy_handler.connecting():
self.report_box.setChecked(False) self.status_lbl.setText(f"Connection is dropped. Reconnecting to {self.ip_addr}:{self.port}.")
await self.kirdy_data_watcher.stop_watching() self.connect_btn.setText("Stop")
if hard_reset: else:
await self.kirdy.device.hard_reset() self.connect_btn.setText("Connect")
await self.kirdy.end_session() self.connect_btn.clicked.connect(self.show_conn_settings_form)
self.status_lbl.setText("Disconnected") self.clear_graphs()
self.status_lbl.setText(f"Disconnected from {self.ip_addr}:{self.port}.")
self.connect_btn.clicked.connect(self.kirdy_handler.end_session)
def _status(self): def _status(self):
# TODO: Get rev no from Kirdy and then add revision into the text # TODO: Get rev no from Kirdy and then add revision into the text
host = self.ip_addr host = self.ip_addr
port = self.port port = self.port
self.status_lbl.setText(f"Connected to Kirdy @ {host}:{port}") self.status_lbl.setText(f"Connected to {host}:{port}")
def clear_graphs(self): def clear_graphs(self):
self.graphs.clear_data_pts() self.graphs.clear_data_pts()
@asyncSlot(dict) @pyqtSlot(dict)
async def graphs_update(self, report): def graphs_update(self, report):
self.graphs.plot_append(report) self.graphs.plot_append(report)
@asyncSlot(dict) @pyqtSlot(dict)
async def update_ld_ctrl_panel_settings(self, settings): def update_ld_ctrl_panel_settings(self, settings):
try: try:
settings = settings['laser'] settings = settings['laser']
with QSignalBlocker(self.params[1]): with QSignalBlocker(self.params[1]):
@ -832,8 +809,8 @@ class MainWindow(QtWidgets.QMainWindow):
except Exception as e: except Exception as e:
logging.error(f"Params tree cannot be updated. Data:{settings}", exc_info=True) logging.error(f"Params tree cannot be updated. Data:{settings}", exc_info=True)
@asyncSlot(dict) @pyqtSlot(dict)
async def update_ld_ctrl_panel_readings(self, report): def update_ld_ctrl_panel_readings(self, report):
try: try:
report = report['laser'] report = report['laser']
with QSignalBlocker(self.params[0]): with QSignalBlocker(self.params[0]):
@ -851,8 +828,8 @@ class MainWindow(QtWidgets.QMainWindow):
except Exception as e: except Exception as e:
logging.error(f"Params tree cannot be updated. Data:{report}", exc_info=True) logging.error(f"Params tree cannot be updated. Data:{report}", exc_info=True)
@asyncSlot(dict) @pyqtSlot(dict)
async def update_thermostat_ctrl_panel_settings(self, settings): def update_thermostat_ctrl_panel_settings(self, settings):
try: try:
settings = settings['thermostat'] settings = settings['thermostat']
with QSignalBlocker(self.params[3]): with QSignalBlocker(self.params[3]):
@ -879,8 +856,8 @@ class MainWindow(QtWidgets.QMainWindow):
except Exception as e: except Exception as e:
logging.error(f"Params tree cannot be updated. Data:{settings}", exc_info=True) logging.error(f"Params tree cannot be updated. Data:{settings}", exc_info=True)
@asyncSlot(dict) @pyqtSlot(dict)
async def update_thermostat_ctrl_panel_readings(self, report): def update_thermostat_ctrl_panel_readings(self, report):
try: try:
report = report['thermostat'] report = report['thermostat']
with QSignalBlocker(self.params[2]): with QSignalBlocker(self.params[2]):
@ -899,12 +876,9 @@ class MainWindow(QtWidgets.QMainWindow):
def set_max_samples(self, samples: int): def set_max_samples(self, samples: int):
self.graphs.set_max_samples(samples) self.graphs.set_max_samples(samples)
@asyncSlot(int)
async def on_report_box_stateChanged(self, enabled):
await self.kirdy_data_watcher.set_report_mode(enabled)
@asyncSlot() @pyqtSlot()
async def update_net_settings(self): def update_net_settings(self):
net_settings = self.update_net_settings_form.get_net_settings() net_settings = self.update_net_settings_form.get_net_settings()
if net_settings is None: if net_settings is None:
self.status_lbl.setText("Invalid IP Settings Input") self.status_lbl.setText("Invalid IP Settings Input")
@ -913,11 +887,11 @@ class MainWindow(QtWidgets.QMainWindow):
port = net_settings["port"] port = net_settings["port"]
prefix_len = net_settings["prefix_len"] prefix_len = net_settings["prefix_len"]
gateway = net_settings["gateway_addr"] gateway = net_settings["gateway_addr"]
await self.kirdy.device.set_ip_settings(addr, port, prefix_len, gateway) self.kirdy.device.set_ip_settings(addr, port, prefix_len, gateway)
self.status_lbl.setText("IP Settings is Updated") self.status_lbl.setText("IP Settings is Updated")
@asyncSlot() @pyqtSlot()
async def start_connecting(self): def start_connecting(self):
net_settings = self.conn_settings_form.get_net_settings() net_settings = self.conn_settings_form.get_net_settings()
if net_settings is None: if net_settings is None:
self.status_lbl.setText("Invalid IP Settings Input") self.status_lbl.setText("Invalid IP Settings Input")
@ -927,25 +901,17 @@ class MainWindow(QtWidgets.QMainWindow):
host = self.ip_addr host = self.ip_addr
port = self.port port = self.port
try:
if not (self.kirdy.connecting() or self.kirdy.connected()):
self.status_lbl.setText("Connecting...")
await self.kirdy.start_session(host=host, port=port, timeout=5.0)
await self._on_connection_changed(True)
else:
await self.bail()
except (OSError, TimeoutError, ConnectionResetError) as e:
logging.error(f"Failed communicating to {host}:{port}: {e}")
await self.bail()
self.status_lbl.setText(f"Cannot connect to Kirdy@ {host}:{port}")
@asyncSlot() if not (self.kirdy_handler.connecting() or self.kirdy_handler.connected()):
async def bail(self): self.status_lbl.setText("Connecting...")
await self._on_connection_changed(False) self.kirdy_handler.start_session(host=host, port=port)
await self.kirdy.end_session()
@asyncSlot(object, object) self.connect_btn.setText("Stop")
async def send_command(self, param, changes): self.connect_btn.clicked.disconnect()
self.connect_btn.clicked.connect(self.kirdy_handler.end_session)
@pyqtSlot(object, object)
def send_command(self, param, changes):
for inner_param, change, data in changes: for inner_param, change, data in changes:
if change == 'value': if change == 'value':
""" cmd translation from mutex type parameter """ """ cmd translation from mutex type parameter """
@ -953,7 +919,7 @@ class MainWindow(QtWidgets.QMainWindow):
target, action = inner_param.opts['target_action_pair'][inner_param.opts['limits'].index(data)] target, action = inner_param.opts['target_action_pair'][inner_param.opts['limits'].index(data)]
cmd = getattr(getattr(self.kirdy, target), action) cmd = getattr(getattr(self.kirdy, target), action)
param.child(*param.childPath(inner_param)).setOpts(lock=True) param.child(*param.childPath(inner_param)).setOpts(lock=True)
await cmd() cmd()
param.child(*param.childPath(inner_param)).setOpts(lock=False) param.child(*param.childPath(inner_param)).setOpts(lock=False)
continue continue
""" cmd translation from non-mutex type parameter""" """ cmd translation from non-mutex type parameter"""
@ -964,28 +930,25 @@ class MainWindow(QtWidgets.QMainWindow):
data = siEval(str(data)+inner_param.opts["unit"], regex=FLOAT_REGEX, suffix=suffix) data = siEval(str(data)+inner_param.opts["unit"], regex=FLOAT_REGEX, suffix=suffix)
cmd = getattr(getattr(self.kirdy, inner_param.opts["target"]), inner_param.opts["action"]) cmd = getattr(getattr(self.kirdy, inner_param.opts["target"]), inner_param.opts["action"])
param.child(*param.childPath(inner_param)).setOpts(lock=True) param.child(*param.childPath(inner_param)).setOpts(lock=True)
await cmd(data) cmd(data)
param.child(*param.childPath(inner_param)).setOpts(lock=False) param.child(*param.childPath(inner_param)).setOpts(lock=False)
continue continue
async def coro_main(): def coro_main():
args = get_argparser().parse_args() args = get_argparser().parse_args()
if args.logLevel: if args.logLevel:
logging.basicConfig(level=getattr(logging, args.logLevel)) logging.basicConfig(level=getattr(logging, args.logLevel))
app_quit_event = asyncio.Event() app = QtWidgets.QApplication(sys.argv)
app = QtWidgets.QApplication.instance()
app.aboutToQuit.connect(app_quit_event.set)
main_window = MainWindow(args) main_window = MainWindow(args)
main_window.show() main_window.show()
await app_quit_event.wait() app.aboutToQuit.connect(main_window.kirdy_handler.end_session)
app.exec()
def main(): def main():
qasync.run(coro_main()) coro_main()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -351,13 +351,13 @@
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>360</width> <width>480</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>360</width> <width>480</width>
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
@ -448,140 +448,6 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item>
<layout class="QHBoxLayout" name="report_layout" stretch="0,1,1,1">
<property name="spacing">
<number>6</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="report_lbl">
<property name="text">
<string>Poll every: </string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="report_refresh_spin">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>70</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>70</width>
<height>16777215</height>
</size>
</property>
<property name="baseSize">
<size>
<width>70</width>
<height>0</height>
</size>
</property>
<property name="suffix">
<string> s</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>0.100000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="stepType">
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="report_box">
<property name="enabled">
<bool>false</bool>
</property>
<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>
<widget class="QPushButton" name="report_apply_btn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</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>Apply</string>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>