2023-07-26 13:03:16 +08:00
from PyQt6 import QtWidgets , QtGui , QtCore
2023-06-27 17:34:39 +08:00
from PyQt6 . QtCore import pyqtSignal , QObject , QSignalBlocker , pyqtSlot
2023-08-23 10:53:51 +08:00
import pyqtgraph . parametertree . parameterTypes as pTypes
from pyqtgraph . parametertree import Parameter , ParameterTree , ParameterItem , registerParameterType
2023-05-19 11:23:39 +08:00
import pyqtgraph as pg
2023-07-06 16:06:33 +08:00
from pglive . sources . data_connector import DataConnector
from pglive . kwargs import Axis
from pglive . sources . live_plot import LiveLinePlot
from pglive . sources . live_plot_widget import LivePlotWidget
from pglive . sources . live_axis import LiveAxis
2023-05-19 11:23:39 +08:00
import sys
import argparse
import logging
2023-06-26 10:20:48 +08:00
import asyncio
2023-07-31 12:33:00 +08:00
from pytec . aioclient import Client , StoppedConnecting
2023-06-27 17:34:39 +08:00
import qasync
from qasync import asyncSlot , asyncClose
2023-08-30 15:23:03 +08:00
from autotune import PIDAutotune , PIDAutotuneState
2023-05-19 11:23:39 +08:00
# pyuic6 -x tec_qt.ui -o ui_tec_qt.py
from ui_tec_qt import Ui_MainWindow
def get_argparser ( ) :
parser = argparse . ArgumentParser ( description = " ARTIQ master " )
parser . add_argument ( " --connect " , default = None , action = " store_true " ,
help = " Automatically connect to the specified Thermostat in IP:port format " )
parser . add_argument ( ' IP ' , metavar = " ip " , default = None , nargs = ' ? ' )
parser . add_argument ( ' PORT ' , metavar = " port " , default = None , nargs = ' ? ' )
parser . add_argument ( " -l " , " --log " , dest = " logLevel " , choices = [ ' DEBUG ' , ' INFO ' , ' WARNING ' , ' ERROR ' , ' CRITICAL ' ] ,
help = " Set the logging level " )
return parser
2023-08-23 10:53:51 +08:00
class MutexParameter ( pTypes . ListParameter ) :
"""
Mutually exclusive parameter where only one of its children is visible at a time , list selectable .
The ordering of the list items determines which children will be visible .
"""
def __init__ ( self , * * opts ) :
super ( ) . __init__ ( * * opts )
2023-08-29 13:11:25 +08:00
self . sigValueChanged . connect ( self . show_chosen_child )
2023-08-23 10:53:51 +08:00
self . sigValueChanged . emit ( self , self . opts [ ' value ' ] )
def _get_param_from_value ( self , value ) :
if isinstance ( self . opts [ ' limits ' ] , dict ) :
values_list = list ( self . opts [ ' limits ' ] . values ( ) )
else :
values_list = self . opts [ ' limits ' ]
return self . children ( ) [ values_list . index ( value ) ]
@pyqtSlot ( object , object )
2023-08-29 13:11:25 +08:00
def show_chosen_child ( self , value ) :
2023-08-23 10:53:51 +08:00
for param in self . children ( ) :
param . hide ( )
2023-08-25 23:44:01 +08:00
child_to_show = self . _get_param_from_value ( value . value ( ) )
child_to_show . show ( )
if child_to_show . opts . get ( ' triggerOnShow ' , None ) :
child_to_show . sigValueChanged . emit ( child_to_show , child_to_show . value ( ) )
2023-08-23 10:53:51 +08:00
registerParameterType ( ' mutex ' , MutexParameter )
2023-08-09 11:15:29 +08:00
class WrappedClient ( QObject , Client ) :
connection_error = pyqtSignal ( )
2023-08-11 17:09:33 +08:00
def __init__ ( self , parent ) :
super ( ) . __init__ ( parent )
2023-08-09 11:15:29 +08:00
async def _read_line ( self ) :
try :
return await super ( ) . _read_line ( )
except ( OSError , TimeoutError , asyncio . TimeoutError ) as e : # TODO: Remove asyncio.TimeoutError in Python 3.11
logging . error ( " Client connection error, disconnecting " , exc_info = True )
self . connection_error . emit ( )
2023-08-11 17:09:33 +08:00
async def _check_zero_limits ( self ) :
pwm_report = await self . get_pwm ( )
for pwm_channel in pwm_report :
if ( neg := pwm_channel [ " max_i_neg " ] [ " value " ] ) != ( pos := pwm_channel [ " max_i_pos " ] [ " value " ] ) :
# Set the minimum of the 2
lcd = min ( neg , pos )
await self . set_param ( " pwm " , pwm_channel [ " channel " ] , ' max_i_neg ' , lcd )
await self . set_param ( " pwm " , pwm_channel [ " channel " ] , ' max_i_pos ' , lcd )
for limit in [ " max_i_pos " , " max_v " ] :
if pwm_channel [ limit ] [ " value " ] == 0.0 :
QtWidgets . QMessageBox . warning ( self . parent ( ) , " Limits " , " Max {} is set to zero on channel {} ! " . format ( " Current " if limit == " max_i_pos " else " Voltage " , pwm_channel [ " channel " ] ) )
2023-08-09 11:15:29 +08:00
2023-06-26 10:20:48 +08:00
class ClientWatcher ( QObject ) :
2023-07-11 12:27:43 +08:00
fan_update = pyqtSignal ( dict )
pwm_update = pyqtSignal ( list )
report_update = pyqtSignal ( list )
pid_update = pyqtSignal ( list )
2023-07-20 13:49:11 +08:00
thermistor_update = pyqtSignal ( list )
2023-08-01 13:32:06 +08:00
postfilter_update = pyqtSignal ( list )
2023-05-19 13:45:01 +08:00
2023-07-05 16:25:13 +08:00
def __init__ ( self , parent , client , update_s ) :
2023-08-16 12:25:51 +08:00
self . _update_s = update_s
self . _client = client
self . _watch_task = None
2023-08-16 13:07:26 +08:00
self . _report_mode_task = None
2023-08-16 12:25:51 +08:00
self . _poll_for_report = True
2023-05-19 13:45:01 +08:00
super ( ) . __init__ ( parent )
2023-06-26 10:20:48 +08:00
async def run ( self ) :
2023-08-08 17:16:11 +08:00
loop = asyncio . get_running_loop ( )
2023-07-13 15:45:08 +08:00
while True :
2023-08-08 17:16:11 +08:00
time = loop . time ( )
2023-06-27 17:34:39 +08:00
await self . update_params ( )
2023-08-16 12:25:51 +08:00
await asyncio . sleep ( self . _update_s - ( loop . time ( ) - time ) )
2023-05-19 13:45:01 +08:00
2023-06-27 17:34:39 +08:00
async def update_params ( self ) :
2023-08-16 12:25:51 +08:00
self . fan_update . emit ( await self . _client . get_fan ( ) )
self . pwm_update . emit ( await self . _client . get_pwm ( ) )
if self . _poll_for_report :
self . report_update . emit ( await self . _client . report ( ) )
self . pid_update . emit ( await self . _client . get_pid ( ) )
self . thermistor_update . emit ( await self . _client . get_steinhart_hart ( ) )
self . postfilter_update . emit ( await self . _client . get_postfilter ( ) )
2023-05-19 13:45:01 +08:00
2023-07-06 11:21:56 +08:00
def start_watching ( self ) :
2023-08-16 12:25:51 +08:00
self . _watch_task = asyncio . create_task ( self . run ( ) )
2023-07-06 11:21:56 +08:00
2023-05-19 13:45:01 +08:00
@pyqtSlot ( )
def stop_watching ( self ) :
2023-08-16 12:25:51 +08:00
if self . _watch_task is not None :
self . _watch_task . cancel ( )
self . _watch_task = None
2023-05-19 13:45:01 +08:00
2023-08-16 13:07:26 +08:00
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 ( )
if self . _report_mode_task is not None :
await self . _report_mode_task
self . _report_mode_task = None
async def report_mode ( self ) :
async for report in self . _client . report_mode ( ) :
self . report_update . emit ( report )
2023-07-11 12:27:43 +08:00
@pyqtSlot ( float )
2023-07-01 23:46:40 +08:00
def set_update_s ( self , update_s ) :
2023-08-16 12:25:51 +08:00
self . _update_s = update_s
2023-07-01 23:46:40 +08:00
2023-08-28 12:46:51 +08:00
class ChannelGraphs :
""" The maximum number of sample points to store. """
DEFAULT_MAX_SAMPLES = 1000
def __init__ ( self , t_widget , i_widget ) :
self . _t_widget = t_widget
self . _i_widget = i_widget
self . _t_plot = LiveLinePlot ( )
2023-08-31 13:25:39 +08:00
self . _i_plot = LiveLinePlot ( name = ' Measured ' )
self . _iset_plot = LiveLinePlot ( name = ' Set ' , pen = pg . mkPen ( ' r ' ) )
2023-08-28 12:46:51 +08:00
self . t_line = self . _t_widget . getPlotItem ( ) . addLine ( label = ' {value} °C ' )
self . t_line . setVisible ( False )
for graph in t_widget , i_widget :
time_axis = LiveAxis ( ' bottom ' , text = " Time since Thermostat reset " , * * { Axis . TICK_FORMAT : Axis . DURATION } )
time_axis . showLabel ( )
graph . setAxisItems ( { ' bottom ' : time_axis } )
graph . add_crosshair ( pg . mkPen ( color = ' red ' , width = 1 ) , { ' color ' : ' green ' } )
# Enable linking of axes in the graph widget's context menu
graph . register ( graph . getPlotItem ( ) . titleLabel . text ) # Slight hack getting the title
temperature_axis = LiveAxis ( ' left ' , text = " Temperature " , units = " °C " )
temperature_axis . showLabel ( )
t_widget . setAxisItems ( { ' left ' : temperature_axis } )
current_axis = LiveAxis ( ' left ' , text = " Current " , units = " A " )
current_axis . showLabel ( )
i_widget . setAxisItems ( { ' left ' : current_axis } )
i_widget . addLegend ( brush = ( 50 , 50 , 200 , 150 ) )
t_widget . addItem ( self . _t_plot )
i_widget . addItem ( self . _iset_plot )
2023-08-31 13:25:39 +08:00
i_widget . addItem ( self . _i_plot )
2023-08-28 12:46:51 +08:00
self . t_connector = DataConnector ( self . _t_plot , max_points = self . DEFAULT_MAX_SAMPLES )
self . i_connector = DataConnector ( self . _i_plot , max_points = self . DEFAULT_MAX_SAMPLES )
self . iset_connector = DataConnector ( self . _iset_plot , max_points = self . DEFAULT_MAX_SAMPLES )
self . max_samples = self . DEFAULT_MAX_SAMPLES
def plot_append ( self , report ) :
temperature = report [ ' temperature ' ]
current = report [ ' tec_i ' ]
iset = report [ ' i_set ' ]
time = report [ ' time ' ]
if temperature is not None :
self . t_connector . cb_append_data_point ( temperature , time )
self . i_connector . cb_append_data_point ( current , time )
self . iset_connector . cb_append_data_point ( iset , time )
def clear ( self ) :
for connector in self . t_connector , self . i_connector , self . iset_connector :
connector . clear ( )
2023-07-01 23:46:40 +08:00
class MainWindow ( QtWidgets . QMainWindow , Ui_MainWindow ) :
2023-07-06 16:06:33 +08:00
""" The maximum number of sample points to store. """
DEFAULT_MAX_SAMPLES = 1000
2023-08-20 21:32:32 +08:00
""" Thermostat parameters that are particular to a channel """
THERMOSTAT_PARAMETERS = [ [
2023-09-11 10:45:51 +08:00
{ ' name ' : ' Temperature ' , ' type ' : ' float ' , ' siPrefix ' : True , ' suffix ' : ' °C ' , ' decimals ' : 6 , ' readonly ' : True } ,
2023-08-22 13:22:07 +08:00
{ ' name ' : ' Current through TEC ' , ' type ' : ' float ' , ' siPrefix ' : True , ' suffix ' : ' A ' , ' readonly ' : True } ,
{ ' name ' : ' Output Config ' , ' expanded ' : True , ' type ' : ' group ' , ' children ' : [
2023-08-29 12:06:48 +08:00
{ ' name ' : ' Control Method ' , ' type ' : ' mutex ' , ' limits ' : { ' Constant Current ' : False , ' Temperature PID ' : True } ,
2023-08-30 10:17:39 +08:00
' param ' : [ ( ' pwm ' , ch , ' pid ' ) ] , ' children ' : [
2023-08-29 12:06:48 +08:00
{ ' name ' : ' Set Current ' , ' type ' : ' float ' , ' value ' : 0 , ' step ' : 0.1 , ' limits ' : ( - 3 , 3 ) , ' siPrefix ' : True , ' triggerOnShow ' : True ,
2023-08-22 13:22:07 +08:00
' suffix ' : ' A ' , ' param ' : [ ( ' pwm ' , ch , ' i_set ' ) ] } ,
{ ' name ' : ' Set Temperature ' , ' type ' : ' float ' , ' value ' : 25 , ' step ' : 0.1 , ' limits ' : ( - 273 , 300 ) , ' siPrefix ' : True ,
' suffix ' : ' °C ' , ' param ' : [ ( ' pid ' , ch , ' target ' ) ] } ,
] } ,
{ ' name ' : ' Limits ' , ' expanded ' : False , ' type ' : ' group ' , ' children ' : [
{ ' name ' : ' Max Absolute Current ' , ' type ' : ' float ' , ' value ' : 0 , ' step ' : 0.1 , ' limits ' : ( 0 , 3 ) , ' siPrefix ' : True ,
2023-08-29 12:59:39 +08:00
' suffix ' : ' A ' , ' param ' : [ ( ' pwm ' , ch , ' max_i_pos ' ) , ( ' pid ' , ch , ' output_min ' , ' - ' ) , ( ' pwm ' , ch , ' max_i_neg ' ) , ( ' pid ' , ch , ' output_max ' ) ] } ,
2023-08-22 13:22:07 +08:00
{ ' name ' : ' Max Absolute Voltage ' , ' type ' : ' float ' , ' value ' : 0 , ' step ' : 0.1 , ' limits ' : ( 0 , 5 ) , ' siPrefix ' : True ,
' suffix ' : ' V ' , ' param ' : [ ( ' pwm ' , ch , ' max_v ' ) ] } ,
] }
2023-08-20 21:32:32 +08:00
] } ,
{ ' name ' : ' Thermistor Config ' , ' expanded ' : False , ' type ' : ' group ' , ' children ' : [
{ ' name ' : ' T₀ ' , ' type ' : ' float ' , ' value ' : 25 , ' step ' : 0.1 , ' limits ' : ( - 100 , 100 ) , ' siPrefix ' : True ,
2023-08-22 15:37:51 +08:00
' suffix ' : ' °C ' , ' param ' : [ ( ' s-h ' , ch , ' t0 ' ) ] } ,
2023-08-20 21:32:32 +08:00
{ ' name ' : ' R₀ ' , ' type ' : ' float ' , ' value ' : 10000 , ' step ' : 1 , ' siPrefix ' : True , ' suffix ' : ' Ω ' ,
2023-08-22 15:37:51 +08:00
' param ' : [ ( ' s-h ' , ch , ' r0 ' ) ] } ,
2023-09-11 12:10:05 +08:00
{ ' name ' : ' B ' , ' type ' : ' float ' , ' value ' : 3950 , ' step ' : 1 , ' suffix ' : ' K ' , ' decimals ' : 4 , ' param ' : [ ( ' s-h ' , ch , ' b ' ) ] } ,
2023-08-20 21:32:32 +08:00
] } ,
{ ' name ' : ' Postfilter Config ' , ' expanded ' : False , ' type ' : ' group ' , ' children ' : [
2023-08-29 12:39:32 +08:00
{ ' name ' : ' Postfilter Rate ' , ' type ' : ' list ' , ' value ' : ( ' rate ' , 16.67 ) , ' param ' : [ ( ' postfilter ' , ch ) ] ,
' limits ' : { ' Off ' : ( ' off ' , ) , ' 16.67 Hz ' : ( ' rate ' , 16.67 ) , ' 20 Hz ' : ( ' rate ' , 20.0 ) , ' 21.25 Hz ' : ( ' rate ' , 21.25 ) , ' 27 Hz ' : ( ' rate ' , 27.0 ) } } ,
2023-08-20 21:32:32 +08:00
] } ,
{ ' name ' : ' PID Config ' , ' expanded ' : False , ' type ' : ' group ' , ' children ' : [
2023-09-11 12:10:05 +08:00
{ ' name ' : ' Kp ' , ' type ' : ' float ' , ' step ' : 0.1 , ' suffix ' : ' ' , ' param ' : [ ( ' pid ' , ch , ' kp ' ) ] } ,
{ ' name ' : ' Ki ' , ' type ' : ' float ' , ' step ' : 0.1 , ' suffix ' : ' Hz ' , ' param ' : [ ( ' pid ' , ch , ' ki ' ) ] } ,
{ ' name ' : ' Kd ' , ' type ' : ' float ' , ' step ' : 0.1 , ' suffix ' : ' s ' , ' param ' : [ ( ' pid ' , ch , ' kd ' ) ] } ,
2023-08-20 21:32:32 +08:00
{ ' name ' : ' PID Auto Tune ' , ' expanded ' : False , ' type ' : ' group ' , ' children ' : [
{ ' name ' : ' Target Temperature ' , ' type ' : ' float ' , ' value ' : 20 , ' step ' : 0.1 , ' siPrefix ' : True , ' suffix ' : ' °C ' } ,
{ ' name ' : ' Test Current ' , ' type ' : ' float ' , ' value ' : 1 , ' step ' : 0.1 , ' siPrefix ' : True , ' suffix ' : ' A ' } ,
{ ' name ' : ' Temperature Swing ' , ' type ' : ' float ' , ' value ' : 1.5 , ' step ' : 0.1 , ' siPrefix ' : True , ' prefix ' : ' ± ' , ' suffix ' : ' °C ' } ,
2023-09-01 15:55:14 +08:00
{ ' name ' : ' Run ' , ' type ' : ' action ' , ' tip ' : ' Run ' } ,
2023-08-20 21:32:32 +08:00
] } ,
] } ,
2023-08-29 12:27:19 +08:00
{ ' name ' : ' Save to flash ' , ' type ' : ' action ' , ' tip ' : ' Save config to thermostat, applies on reset ' } ,
{ ' name ' : ' Load from flash ' , ' type ' : ' action ' , ' tip ' : ' Load config from flash ' }
2023-08-20 21:32:32 +08:00
] for ch in range ( 2 ) ]
2023-07-01 23:46:40 +08:00
def __init__ ( self , args ) :
super ( ) . __init__ ( )
self . setupUi ( self )
2023-09-11 12:24:45 +08:00
self . ch0_t_graph . setTitle ( " Channel 0 Temperature " )
self . ch0_i_graph . setTitle ( " Channel 0 Current " )
self . ch1_t_graph . setTitle ( " Channel 1 Temperature " )
self . ch1_i_graph . setTitle ( " Channel 1 Current " )
2023-07-06 16:06:33 +08:00
self . max_samples = self . DEFAULT_MAX_SAMPLES
2023-07-31 16:19:07 +08:00
self . _set_up_connection_menu ( )
self . _set_up_thermostat_menu ( )
self . _set_up_plot_menu ( )
2023-07-19 11:38:04 +08:00
2023-08-11 17:09:33 +08:00
self . client = WrappedClient ( self )
2023-08-09 11:15:29 +08:00
self . client . connection_error . connect ( self . bail )
2023-07-31 16:36:48 +08:00
self . client_watcher = ClientWatcher ( self , self . client , self . report_refresh_spin . value ( ) )
2023-07-14 16:10:59 +08:00
self . client_watcher . fan_update . connect ( self . fan_update )
2023-07-20 13:47:39 +08:00
self . client_watcher . report_update . connect ( self . update_report )
2023-07-20 13:48:33 +08:00
self . client_watcher . pid_update . connect ( self . update_pid )
2023-07-20 13:49:23 +08:00
self . client_watcher . pwm_update . connect ( self . update_pwm )
2023-07-20 13:49:11 +08:00
self . client_watcher . thermistor_update . connect ( self . update_thermistor )
2023-08-01 13:32:06 +08:00
self . client_watcher . postfilter_update . connect ( self . update_postfilter )
2023-07-14 16:10:59 +08:00
self . report_apply_btn . clicked . connect (
lambda : self . client_watcher . set_update_s ( self . report_refresh_spin . value ( ) )
)
2023-07-01 23:46:40 +08:00
2023-08-29 12:24:31 +08:00
self . params = [
Parameter . create ( name = f " Thermostat Channel { ch } Parameters " , type = ' group ' , value = ch , children = self . THERMOSTAT_PARAMETERS [ ch ] )
for ch in range ( 2 )
]
self . _set_param_tree ( )
self . channel_graphs = [
ChannelGraphs ( getattr ( self , f ' ch { ch } _t_graph ' ) , getattr ( self , f ' ch { ch } _i_graph ' ) )
for ch in range ( 2 )
]
2023-08-30 15:23:03 +08:00
self . autotuners = [
PIDAutotune ( 25 )
for _ in range ( 2 )
]
2023-09-01 15:55:14 +08:00
self . loading_spinner . hide ( )
2023-08-29 12:24:31 +08:00
self . hw_rev_data = None
2023-07-01 23:46:40 +08:00
if args . connect :
if args . IP :
2023-07-31 13:06:24 +08:00
self . host_set_line . setText ( args . IP )
2023-07-01 23:46:40 +08:00
if args . PORT :
self . port_set_spin . setValue ( int ( args . PORT ) )
self . connect_btn . click ( )
2023-07-31 16:19:07 +08:00
def _set_up_connection_menu ( self ) :
2023-07-26 16:43:24 +08:00
self . connection_menu = QtWidgets . QMenu ( )
self . connection_menu . setTitle ( ' Connection Settings ' )
2023-07-19 12:49:15 +08:00
2023-07-31 13:06:24 +08:00
self . host_set_line = QtWidgets . QLineEdit ( )
self . host_set_line . setMinimumSize ( QtCore . QSize ( 160 , 0 ) )
self . host_set_line . setMaximumSize ( QtCore . QSize ( 160 , 16777215 ) )
self . host_set_line . setMaxLength ( 15 )
self . host_set_line . setClearButtonEnabled ( True )
2023-07-26 16:01:57 +08:00
2023-07-31 13:10:57 +08:00
self . host_set_line . setText ( " 192.168.1.26 " )
2023-09-11 12:24:45 +08:00
self . host_set_line . setPlaceholderText ( " IP for the Thermostat " )
2023-07-26 16:01:57 +08:00
2023-07-26 16:43:24 +08:00
host = QtWidgets . QWidgetAction ( self . connection_menu )
2023-07-31 13:06:24 +08:00
host . setDefaultWidget ( self . host_set_line )
2023-07-26 16:43:24 +08:00
self . connection_menu . addAction ( host )
self . connection_menu . host = host
2023-07-26 16:01:57 +08:00
2023-07-26 13:22:27 +08:00
self . port_set_spin = QtWidgets . QSpinBox ( )
self . port_set_spin . setMinimumSize ( QtCore . QSize ( 70 , 0 ) )
self . port_set_spin . setMaximumSize ( QtCore . QSize ( 70 , 16777215 ) )
self . port_set_spin . setMaximum ( 65535 )
2023-08-18 10:32:48 +08:00
self . port_set_spin . setValue ( 23 )
2023-07-26 13:22:27 +08:00
2023-07-26 16:43:24 +08:00
port = QtWidgets . QWidgetAction ( self . connection_menu )
2023-07-19 12:49:15 +08:00
port . setDefaultWidget ( self . port_set_spin )
2023-07-26 16:43:24 +08:00
self . connection_menu . addAction ( port )
self . connection_menu . port = port
self . connect_btn . setMenu ( self . connection_menu )
2023-07-31 16:19:07 +08:00
def _set_up_thermostat_menu ( self ) :
self . thermostat_menu = QtWidgets . QMenu ( )
self . thermostat_menu . setTitle ( ' Thermostat settings ' )
2023-07-19 12:49:15 +08:00
2023-07-26 13:03:16 +08:00
self . fan_group = QtWidgets . QWidget ( )
self . fan_group . setEnabled ( False )
self . fan_group . setMinimumSize ( QtCore . QSize ( 40 , 0 ) )
2023-08-18 11:20:35 +08:00
self . fan_layout = QtWidgets . QHBoxLayout ( self . fan_group )
self . fan_layout . setSpacing ( 9 )
2023-07-26 13:03:16 +08:00
self . fan_lbl = QtWidgets . QLabel ( parent = self . fan_group )
self . fan_lbl . setMinimumSize ( QtCore . QSize ( 40 , 0 ) )
self . fan_lbl . setMaximumSize ( QtCore . QSize ( 40 , 16777215 ) )
self . fan_lbl . setBaseSize ( QtCore . QSize ( 40 , 0 ) )
2023-08-18 11:20:35 +08:00
self . fan_layout . addWidget ( self . fan_lbl )
2023-07-26 13:03:16 +08:00
self . fan_power_slider = QtWidgets . QSlider ( parent = self . fan_group )
self . fan_power_slider . setMinimumSize ( QtCore . QSize ( 200 , 0 ) )
self . fan_power_slider . setMaximumSize ( QtCore . QSize ( 200 , 16777215 ) )
self . fan_power_slider . setBaseSize ( QtCore . QSize ( 200 , 0 ) )
2023-08-18 11:18:04 +08:00
self . fan_power_slider . setRange ( 1 , 100 )
2023-07-26 13:03:16 +08:00
self . fan_power_slider . setOrientation ( QtCore . Qt . Orientation . Horizontal )
2023-08-18 11:20:35 +08:00
self . fan_layout . addWidget ( self . fan_power_slider )
2023-07-26 13:03:16 +08:00
self . fan_auto_box = QtWidgets . QCheckBox ( parent = self . fan_group )
self . fan_auto_box . setMinimumSize ( QtCore . QSize ( 70 , 0 ) )
self . fan_auto_box . setMaximumSize ( QtCore . QSize ( 70 , 16777215 ) )
2023-08-18 11:20:35 +08:00
self . fan_layout . addWidget ( self . fan_auto_box )
2023-07-26 14:02:55 +08:00
self . fan_pwm_warning = QtWidgets . QLabel ( parent = self . fan_group )
self . fan_pwm_warning . setMinimumSize ( QtCore . QSize ( 16 , 0 ) )
2023-08-18 11:20:35 +08:00
self . fan_layout . addWidget ( self . fan_pwm_warning )
2023-07-26 13:03:16 +08:00
2023-07-31 16:21:15 +08:00
self . fan_power_slider . valueChanged . connect ( self . fan_set )
self . fan_auto_box . stateChanged . connect ( self . fan_auto_set )
2023-09-11 12:24:45 +08:00
self . fan_lbl . setToolTip ( " Adjust the fan " )
self . fan_lbl . setText ( " Fan: " )
self . fan_auto_box . setText ( " Auto " )
2023-07-26 13:03:16 +08:00
2023-07-31 16:19:07 +08:00
fan = QtWidgets . QWidgetAction ( self . thermostat_menu )
2023-07-19 15:04:12 +08:00
fan . setDefaultWidget ( self . fan_group )
2023-07-31 16:19:07 +08:00
self . thermostat_menu . addAction ( fan )
self . thermostat_menu . fan = fan
2023-07-19 15:04:12 +08:00
2023-07-26 17:46:21 +08:00
@asyncSlot ( bool )
async def reset_thermostat ( _ ) :
await self . _on_connection_changed ( False )
2023-07-31 16:36:48 +08:00
await self . client . reset ( )
2023-07-26 17:46:21 +08:00
await asyncio . sleep ( 0.1 ) # Wait for the reset to start
self . connect_btn . click ( ) # Reconnect
self . actionReset . triggered . connect ( reset_thermostat )
2023-07-31 16:19:07 +08:00
self . thermostat_menu . addAction ( self . actionReset )
2023-07-26 17:46:21 +08:00
2023-08-01 10:33:53 +08:00
@asyncSlot ( bool )
async def dfu_mode ( _ ) :
await self . _on_connection_changed ( False )
await self . client . dfu ( )
# TODO: add a firmware flashing GUI?
self . actionEnter_DFU_Mode . triggered . connect ( dfu_mode )
2023-07-31 16:19:07 +08:00
self . thermostat_menu . addAction ( self . actionEnter_DFU_Mode )
2023-08-01 10:33:53 +08:00
2023-08-01 16:48:46 +08:00
@asyncSlot ( bool )
async def network_settings ( _ ) :
ask_network = QtWidgets . QInputDialog ( self )
2023-09-11 12:24:45 +08:00
ask_network . setWindowTitle ( " Network Settings " )
ask_network . setLabelText ( " Set the Thermostat ' s IPv4 address, netmask and gateway (optional) " )
2023-08-01 16:48:46 +08:00
ask_network . setTextValue ( ( await self . client . ipv4 ( ) ) [ ' addr ' ] )
@pyqtSlot ( str )
def set_ipv4 ( ipv4_settings ) :
sure = QtWidgets . QMessageBox ( self )
2023-09-11 12:24:45 +08:00
sure . setWindowTitle ( " Set network? " )
2023-08-01 16:48:46 +08:00
sure . setText ( f " Setting this as network and disconnecting:<br> { ipv4_settings } " )
@asyncSlot ( object )
async def really_set ( button ) :
await self . client . set_param ( " ipv4 " , ipv4_settings )
await self . client . disconnect ( )
await self . _on_connection_changed ( False )
sure . buttonClicked . connect ( really_set )
sure . show ( )
ask_network . textValueSelected . connect ( set_ipv4 )
ask_network . show ( )
self . actionNetwork_Settings . triggered . connect ( network_settings )
2023-07-31 16:19:07 +08:00
self . thermostat_menu . addAction ( self . actionNetwork_Settings )
2023-08-01 10:34:41 +08:00
@asyncSlot ( bool )
async def load ( _ ) :
await self . client . load_config ( )
self . actionLoad_all_configs . triggered . connect ( load )
2023-07-31 16:19:07 +08:00
self . thermostat_menu . addAction ( self . actionLoad_all_configs )
2023-08-01 10:34:41 +08:00
@asyncSlot ( bool )
async def save ( _ ) :
await self . client . save_config ( )
self . actionSave_all_configs . triggered . connect ( save )
2023-07-31 16:19:07 +08:00
self . thermostat_menu . addAction ( self . actionSave_all_configs )
2023-07-28 10:42:13 +08:00
2023-07-31 16:14:14 +08:00
def about_thermostat ( ) :
QtWidgets . QMessageBox . about (
self ,
2023-09-11 12:24:45 +08:00
" About Thermostat " ,
2023-07-31 16:14:14 +08:00
f """
2023-08-31 16:42:55 +08:00
< h1 > Sinara 8451 Thermostat v { self . hw_rev_data [ ' rev ' ] [ ' major ' ] } . { self . hw_rev_data [ ' rev ' ] [ ' minor ' ] } < / h1 >
2023-07-31 16:14:14 +08:00
< br >
< h2 > Settings : < / h2 >
Default fan curve :
2023-08-31 16:42:55 +08:00
a = { self . hw_rev_data [ ' settings ' ] [ ' fan_k_a ' ] } ,
b = { self . hw_rev_data [ ' settings ' ] [ ' fan_k_b ' ] } ,
c = { self . hw_rev_data [ ' settings ' ] [ ' fan_k_c ' ] }
2023-07-31 16:14:14 +08:00
< br >
Fan PWM range :
2023-08-31 16:42:55 +08:00
{ self . hw_rev_data [ ' settings ' ] [ ' min_fan_pwm ' ] } – { self . hw_rev_data [ ' settings ' ] [ ' max_fan_pwm ' ] }
2023-07-31 16:14:14 +08:00
< br >
2023-08-31 16:42:55 +08:00
Fan PWM frequency : { self . hw_rev_data [ ' settings ' ] [ ' fan_pwm_freq_hz ' ] } Hz
2023-07-31 16:14:14 +08:00
< br >
2023-08-31 16:42:55 +08:00
Fan available : { self . hw_rev_data [ ' settings ' ] [ ' fan_available ' ] }
2023-07-31 16:14:14 +08:00
< br >
2023-08-31 16:42:55 +08:00
Fan PWM recommended : { self . hw_rev_data [ ' settings ' ] [ ' fan_pwm_recommended ' ] }
2023-07-31 16:14:14 +08:00
"""
)
self . actionAbout_Thermostat . triggered . connect ( about_thermostat )
2023-07-31 16:19:07 +08:00
self . thermostat_menu . addAction ( self . actionAbout_Thermostat )
2023-07-31 16:14:14 +08:00
2023-07-31 16:19:07 +08:00
self . thermostat_settings . setMenu ( self . thermostat_menu )
def _set_up_plot_menu ( self ) :
2023-07-26 13:50:29 +08:00
self . plot_menu = QtWidgets . QMenu ( )
self . plot_menu . setTitle ( " Plot Settings " )
clear = QtGui . QAction ( " Clear graphs " , self . plot_menu )
2023-07-19 13:34:01 +08:00
clear . triggered . connect ( self . clear_graphs )
2023-07-26 13:50:29 +08:00
self . plot_menu . addAction ( clear )
self . plot_menu . clear = clear
2023-07-19 13:34:01 +08:00
2023-07-20 15:54:28 +08:00
self . samples_spinbox = QtWidgets . QSpinBox ( )
self . samples_spinbox . setRange ( 2 , 100000 )
self . samples_spinbox . setSuffix ( ' samples ' )
self . samples_spinbox . setValue ( self . max_samples )
self . samples_spinbox . valueChanged . connect ( self . set_max_samples )
2023-07-26 13:50:29 +08:00
limit_samples = QtWidgets . QWidgetAction ( self . plot_menu )
2023-07-20 15:54:28 +08:00
limit_samples . setDefaultWidget ( self . samples_spinbox )
2023-07-26 13:50:29 +08:00
self . plot_menu . addAction ( limit_samples )
self . plot_menu . limit_samples = limit_samples
2023-07-31 13:22:35 +08:00
self . plot_settings . setMenu ( self . plot_menu )
2023-07-20 15:54:28 +08:00
@pyqtSlot ( int )
def set_max_samples ( self , samples : int ) :
2023-08-28 12:46:51 +08:00
for channel_graph in self . channel_graphs :
channel_graph . t_connector . max_points = samples
channel_graph . i_connector . max_points = samples
channel_graph . iset_connector . max_points = samples
2023-07-06 16:06:33 +08:00
def clear_graphs ( self ) :
2023-08-28 12:46:51 +08:00
for channel_graph in self . channel_graphs :
channel_graph . clear ( )
2023-07-06 16:06:33 +08:00
2023-07-18 10:28:56 +08:00
async def _on_connection_changed ( self , result ) :
2023-07-01 23:46:40 +08:00
self . graph_group . setEnabled ( result )
self . report_group . setEnabled ( result )
2023-07-31 13:25:37 +08:00
self . thermostat_settings . setEnabled ( result )
2023-07-01 23:46:40 +08:00
2023-07-31 13:06:24 +08:00
self . host_set_line . setEnabled ( not result )
2023-07-01 23:46:40 +08:00
self . port_set_spin . setEnabled ( not result )
self . connect_btn . setText ( " Disconnect " if result else " Connect " )
2023-07-20 16:16:57 +08:00
if result :
2023-08-04 11:00:33 +08:00
self . hw_rev_data = await self . client . hw_rev ( )
self . _status ( self . hw_rev_data )
2023-07-20 16:16:57 +08:00
self . client_watcher . start_watching ( )
2023-08-04 12:52:15 +08:00
# await self.client.set_param("fan", 1)
2023-07-20 16:16:57 +08:00
else :
2023-07-13 17:03:49 +08:00
self . status_lbl . setText ( " Disconnected " )
2023-07-07 15:36:17 +08:00
self . fan_pwm_warning . setPixmap ( QtGui . QPixmap ( ) )
self . fan_pwm_warning . setToolTip ( " " )
2023-07-06 16:06:33 +08:00
self . clear_graphs ( )
2023-07-26 09:47:24 +08:00
self . report_box . setChecked ( False )
2023-08-16 13:07:26 +08:00
await self . client_watcher . set_report_mode ( False )
2023-07-18 11:01:51 +08:00
self . client_watcher . stop_watching ( )
2023-08-04 11:00:33 +08:00
self . status_lbl . setText ( " Disconnected " )
2023-07-01 23:46:40 +08:00
2023-07-07 17:19:17 +08:00
def _set_fan_pwm_warning ( self ) :
if self . fan_power_slider . value ( ) != 100 :
2023-07-07 15:36:17 +08:00
pixmapi = getattr ( QtWidgets . QStyle . StandardPixmap , " SP_MessageBoxWarning " )
icon = self . style ( ) . standardIcon ( pixmapi )
self . fan_pwm_warning . setPixmap ( icon . pixmap ( 16 , 16 ) )
2023-07-07 17:44:03 +08:00
self . fan_pwm_warning . setToolTip ( " Throttling the fan (not recommended on this hardware rev) " )
2023-07-07 17:19:17 +08:00
else :
self . fan_pwm_warning . setPixmap ( QtGui . QPixmap ( ) )
self . fan_pwm_warning . setToolTip ( " " )
2023-07-13 17:03:49 +08:00
def _status ( self , hw_rev_d : dict ) :
2023-07-07 17:19:17 +08:00
logging . debug ( hw_rev_d )
2023-07-13 17:03:49 +08:00
self . status_lbl . setText ( f " Connected to Thermostat v { hw_rev_d [ ' rev ' ] [ ' major ' ] } . { hw_rev_d [ ' rev ' ] [ ' minor ' ] } " )
2023-07-07 17:19:17 +08:00
self . fan_group . setEnabled ( hw_rev_d [ " settings " ] [ " fan_available " ] )
2023-07-01 23:46:40 +08:00
2023-07-11 12:27:43 +08:00
@pyqtSlot ( dict )
def fan_update ( self , fan_settings : dict ) :
2023-07-01 23:46:40 +08:00
logging . debug ( fan_settings )
if fan_settings is None :
return
2023-07-05 13:00:56 +08:00
with QSignalBlocker ( self . fan_power_slider ) :
2023-07-11 11:48:38 +08:00
self . fan_power_slider . setValue ( fan_settings [ " fan_pwm " ] or 100 ) # 0 = PWM off = full strength
2023-07-05 13:00:56 +08:00
with QSignalBlocker ( self . fan_auto_box ) :
2023-07-01 23:46:40 +08:00
self . fan_auto_box . setChecked ( fan_settings [ " auto_mode " ] )
2023-07-26 17:44:15 +08:00
if not self . hw_rev_data [ " settings " ] [ " fan_pwm_recommended " ] :
2023-07-07 17:19:17 +08:00
self . _set_fan_pwm_warning ( )
2023-07-01 23:46:40 +08:00
2023-07-11 11:55:30 +08:00
@asyncSlot ( int )
async def fan_set ( self , value ) :
2023-08-09 11:09:28 +08:00
if not self . client . connected ( ) :
2023-07-01 23:46:40 +08:00
return
2023-07-19 14:35:51 +08:00
if self . fan_auto_box . isChecked ( ) :
with QSignalBlocker ( self . fan_auto_box ) :
self . fan_auto_box . setChecked ( False )
2023-08-11 16:08:50 +08:00
await self . client . set_fan ( value )
2023-07-26 17:44:15 +08:00
if not self . hw_rev_data [ " settings " ] [ " fan_pwm_recommended " ] :
2023-07-07 17:19:17 +08:00
self . _set_fan_pwm_warning ( )
2023-07-01 23:46:40 +08:00
@asyncSlot ( int )
async def fan_auto_set ( self , enabled ) :
2023-08-09 11:09:28 +08:00
if not self . client . connected ( ) :
2023-07-01 23:46:40 +08:00
return
if enabled :
2023-08-11 16:08:50 +08:00
await self . client . set_fan ( " auto " )
self . fan_update ( await self . client . get_fan ( ) )
2023-06-27 17:34:39 +08:00
else :
2023-08-11 16:08:50 +08:00
await self . client . set_fan ( self . fan_power_slider . value ( ) )
2023-07-01 23:46:40 +08:00
2023-07-26 09:47:24 +08:00
@asyncSlot ( int )
async def on_report_box_stateChanged ( self , enabled ) :
2023-08-16 13:07:26 +08:00
await self . client_watcher . set_report_mode ( enabled )
2023-07-26 09:47:24 +08:00
2023-07-06 12:39:08 +08:00
@asyncClose
async def closeEvent ( self , event ) :
2023-08-08 16:56:36 +08:00
await self . bail ( )
2023-07-06 12:39:08 +08:00
2023-07-01 23:46:40 +08:00
@asyncSlot ( )
2023-07-18 10:36:29 +08:00
async def on_connect_btn_clicked ( self ) :
2023-08-03 14:42:11 +08:00
host , port = self . host_set_line . text ( ) , self . port_set_spin . value ( )
2023-07-01 23:46:40 +08:00
try :
2023-08-09 11:09:28 +08:00
if not ( self . client . connecting ( ) or self . client . connected ( ) ) :
2023-07-01 23:46:40 +08:00
self . status_lbl . setText ( " Connecting... " )
2023-07-14 16:16:52 +08:00
self . connect_btn . setText ( " Stop " )
2023-07-31 13:06:24 +08:00
self . host_set_line . setEnabled ( False )
2023-07-07 16:31:47 +08:00
self . port_set_spin . setEnabled ( False )
2023-07-31 12:33:00 +08:00
try :
2023-08-09 11:13:43 +08:00
await self . client . start_session ( host = host , port = port , timeout = 30 )
2023-07-31 12:33:00 +08:00
except StoppedConnecting :
2023-07-11 13:43:09 +08:00
return
2023-07-18 10:28:56 +08:00
await self . _on_connection_changed ( True )
2023-07-05 10:24:36 +08:00
else :
2023-08-08 16:56:36 +08:00
await self . bail ( )
2023-07-05 13:13:54 +08:00
2023-07-31 12:33:00 +08:00
except ( OSError , TimeoutError , asyncio . TimeoutError ) as e : # TODO: Remove asyncio.TimeoutError in Python 3.11
2023-08-03 14:42:11 +08:00
logging . error ( f " Failed communicating to { host } : { port } : { e } " )
2023-08-08 16:56:36 +08:00
await self . bail ( )
2023-08-09 11:13:43 +08:00
@asyncSlot ( )
2023-08-08 16:56:36 +08:00
async def bail ( self ) :
await self . _on_connection_changed ( False )
2023-08-09 11:13:43 +08:00
await self . client . end_session ( )
2023-06-27 17:34:39 +08:00
2023-06-28 15:01:47 +08:00
@asyncSlot ( object , object )
async def send_command ( self , param , changes ) :
2023-08-04 12:32:08 +08:00
for inner_param , change , data in changes :
2023-08-23 10:53:51 +08:00
if change == ' value ' :
2023-08-30 10:17:39 +08:00
if inner_param . opts . get ( " param " , None ) is not None :
2023-08-29 11:12:04 +08:00
if inner_param . name ( ) == ' Control Method ' and not data :
return
2023-08-29 12:59:39 +08:00
for thermostat_param in inner_param . opts [ " param " ] :
if len ( thermostat_param ) == 4 : # To tack on prefixes to the data
2023-08-30 11:12:54 +08:00
set_param_args = ( * thermostat_param [ : 3 ] , f ' { thermostat_param [ 3 ] } { data } ' )
2023-08-29 12:39:32 +08:00
elif inner_param . name ( ) == ' Postfilter Rate ' :
2023-08-30 11:12:54 +08:00
set_param_args = ( * thermostat_param , * data )
2023-08-30 10:17:39 +08:00
elif inner_param . name ( ) == ' Control Method ' :
2023-08-30 11:12:54 +08:00
set_param_args = thermostat_param
2023-08-29 12:59:39 +08:00
else :
2023-08-30 11:12:54 +08:00
set_param_args = ( * thermostat_param , data )
await self . client . set_param ( * set_param_args )
2023-08-22 15:37:51 +08:00
2023-06-28 15:01:47 +08:00
def _set_param_tree ( self ) :
2023-08-16 17:35:13 +08:00
for i , tree in enumerate ( ( self . ch0_tree , self . ch1_tree ) ) :
2023-08-16 14:33:16 +08:00
tree . setHeaderHidden ( True )
2023-08-08 11:39:39 +08:00
tree . setParameters ( self . params [ i ] , showTop = False )
self . params [ i ] . sigTreeStateChanged . connect ( self . send_command )
2023-06-28 15:01:47 +08:00
2023-08-29 12:24:31 +08:00
@asyncSlot ( )
2023-08-29 17:30:35 +08:00
async def save ( _ , ch = i ) :
await self . client . save_config ( ch )
2023-08-29 12:24:31 +08:00
self . params [ i ] . child ( ' Save to flash ' ) . sigActivated . connect ( save )
2023-08-29 12:27:19 +08:00
@asyncSlot ( )
2023-08-29 17:30:35 +08:00
async def load ( _ , ch = i ) :
await self . client . load_config ( ch )
2023-08-29 12:27:19 +08:00
self . params [ i ] . child ( ' Load from flash ' ) . sigActivated . connect ( load )
2023-08-30 15:23:03 +08:00
@asyncSlot ( )
async def autotune ( param , ch = i ) :
match self . autotuners [ ch ] . state ( ) :
case PIDAutotuneState . STATE_OFF :
self . autotuners [ ch ] . setParam (
param . parent ( ) . child ( ' Target Temperature ' ) . value ( ) ,
param . parent ( ) . child ( ' Test Current ' ) . value ( ) ,
param . parent ( ) . child ( ' Temperature Swing ' ) . value ( ) ,
self . report_refresh_spin . value ( ) ,
3 )
self . autotuners [ ch ] . setReady ( )
param . setOpts ( title = " Stop " )
self . client_watcher . report_update . connect ( self . autotune_tick )
2023-09-01 15:55:14 +08:00
self . loading_spinner . show ( )
self . loading_spinner . start ( )
if self . autotuners [ 1 - ch ] . state ( ) == PIDAutotuneState . STATE_OFF :
self . background_task_lbl . setText ( " Autotuning channel {ch} ... " . format ( ch = ch ) )
else :
self . background_task_lbl . setText ( " Autotuning channel 0 and 1... " )
2023-08-30 15:23:03 +08:00
case PIDAutotuneState . STATE_READY | PIDAutotuneState . STATE_RELAY_STEP_UP | PIDAutotuneState . STATE_RELAY_STEP_DOWN :
self . autotuners [ ch ] . setOff ( )
param . setOpts ( title = " Run " )
await self . client . set_param ( ' pwm ' , ch , ' i_set ' , 0 )
self . client_watcher . report_update . disconnect ( self . autotune_tick )
2023-09-01 15:55:14 +08:00
if self . autotuners [ 1 - ch ] . state ( ) == PIDAutotuneState . STATE_OFF :
self . background_task_lbl . setText ( " Ready. " )
self . loading_spinner . stop ( )
self . loading_spinner . hide ( )
else :
self . background_task_lbl . setText ( " Autotuning channel {ch} ... " . format ( ch = 1 - ch ) )
2023-08-30 15:23:03 +08:00
self . params [ i ] . child ( ' PID Config ' , ' PID Auto Tune ' , ' Run ' ) . sigActivated . connect ( autotune )
@asyncSlot ( list )
async def autotune_tick ( self , report ) :
for channel_report in report :
channel = channel_report [ ' channel ' ]
match self . autotuners [ channel ] . state ( ) :
case PIDAutotuneState . STATE_READY | PIDAutotuneState . STATE_RELAY_STEP_UP | PIDAutotuneState . STATE_RELAY_STEP_DOWN :
self . autotuners [ channel ] . run ( channel_report [ ' temperature ' ] , channel_report [ ' time ' ] )
await self . client . set_param ( ' pwm ' , channel , ' i_set ' , self . autotuners [ channel ] . output ( ) )
case PIDAutotuneState . STATE_SUCCEEDED :
kp , ki , kd = self . autotuners [ channel ] . get_tec_pid ( )
self . autotuners [ channel ] . setOff ( )
self . params [ channel ] . child ( ' PID Config ' , ' PID Auto Tune ' , ' Run ' ) . setOpts ( title = " Run " )
await self . client . set_param ( ' pid ' , channel , ' kp ' , kp )
await self . client . set_param ( ' pid ' , channel , ' ki ' , ki )
await self . client . set_param ( ' pid ' , channel , ' kd ' , kd )
await self . client . set_param ( ' pwm ' , channel , ' pid ' )
await self . client . set_param ( ' pid ' , channel , ' target ' , self . params [ channel ] . child ( " PID Config " , " PID Auto Tune " , " Target Temperature " ) . value ( ) )
self . client_watcher . report_update . disconnect ( self . autotune_tick )
2023-09-01 15:55:14 +08:00
if self . autotuners [ 1 - channel ] . state ( ) == PIDAutotuneState . STATE_OFF :
self . background_task_lbl . setText ( " Ready. " )
self . loading_spinner . stop ( )
self . loading_spinner . hide ( )
else :
self . background_task_lbl . setText ( " Autotuning channel {ch} ... " . format ( ch = 1 - ch ) )
2023-08-30 15:23:03 +08:00
case PIDAutotuneState . STATE_FAILED :
self . autotuners [ channel ] . setOff ( )
self . params [ channel ] . child ( ' PID Config ' , ' PID Auto Tune ' , ' Run ' ) . setOpts ( title = " Run " )
await self . client . set_param ( ' pwm ' , channel , ' i_set ' , 0 )
self . client_watcher . report_update . disconnect ( self . autotune_tick )
2023-09-01 15:55:14 +08:00
if self . autotuners [ 1 - channel ] . state ( ) == PIDAutotuneState . STATE_OFF :
self . background_task_lbl . setText ( " Ready. " )
self . loading_spinner . stop ( )
self . loading_spinner . hide ( )
else :
self . background_task_lbl . setText ( " Autotuning channel {ch} ... " . format ( ch = 1 - ch ) )
2023-08-30 15:23:03 +08:00
2023-06-28 15:01:47 +08:00
@pyqtSlot ( list )
def update_pid ( self , pid_settings ) :
for settings in pid_settings :
channel = settings [ " channel " ]
2023-08-08 11:39:39 +08:00
with QSignalBlocker ( self . params [ channel ] ) :
self . params [ channel ] . child ( " PID Config " , " Kp " ) . setValue ( settings [ " parameters " ] [ " kp " ] )
self . params [ channel ] . child ( " PID Config " , " Ki " ) . setValue ( settings [ " parameters " ] [ " ki " ] )
self . params [ channel ] . child ( " PID Config " , " Kd " ) . setValue ( settings [ " parameters " ] [ " kd " ] )
2023-08-23 17:06:31 +08:00
self . params [ channel ] . child ( " Output Config " , " Control Method " , " Set Temperature " ) . setValue ( settings [ " target " ] )
2023-08-28 12:46:51 +08:00
self . channel_graphs [ channel ] . t_line . setValue ( settings [ " target " ] )
2023-06-28 15:01:47 +08:00
@pyqtSlot ( list )
def update_report ( self , report_data ) :
for settings in report_data :
channel = settings [ " channel " ]
2023-08-28 12:46:51 +08:00
self . channel_graphs [ channel ] . plot_append ( settings )
2023-08-08 11:39:39 +08:00
with QSignalBlocker ( self . params [ channel ] ) :
2023-08-22 13:22:07 +08:00
self . params [ channel ] . child ( " Output Config " , " Control Method " ) . setValue ( settings [ " pid_engaged " ] )
2023-08-28 12:46:51 +08:00
self . channel_graphs [ channel ] . t_line . setVisible ( settings [ " pid_engaged " ] )
2023-08-29 12:06:48 +08:00
self . params [ channel ] . child ( " Output Config " , " Control Method " , " Set Current " ) . setValue ( settings [ " i_set " ] )
2023-08-23 17:06:31 +08:00
if settings [ ' temperature ' ] is not None and settings [ ' tec_i ' ] is not None :
2023-08-22 13:22:07 +08:00
self . params [ channel ] . child ( " Temperature " ) . setValue ( settings [ ' temperature ' ] )
self . params [ channel ] . child ( " Current through TEC " ) . setValue ( settings [ ' tec_i ' ] )
2023-06-28 15:01:47 +08:00
2023-07-20 13:49:11 +08:00
@pyqtSlot ( list )
def update_thermistor ( self , sh_data ) :
for sh_param in sh_data :
channel = sh_param [ " channel " ]
2023-08-08 11:39:39 +08:00
with QSignalBlocker ( self . params [ channel ] ) :
self . params [ channel ] . child ( " Thermistor Config " , " T₀ " ) . setValue ( sh_param [ " params " ] [ " t0 " ] - 273.15 )
self . params [ channel ] . child ( " Thermistor Config " , " R₀ " ) . setValue ( sh_param [ " params " ] [ " r0 " ] )
2023-08-25 15:53:37 +08:00
self . params [ channel ] . child ( " Thermistor Config " , " B " ) . setValue ( sh_param [ " params " ] [ " b " ] )
2023-07-20 13:49:11 +08:00
2023-07-20 13:49:23 +08:00
@pyqtSlot ( list )
def update_pwm ( self , pwm_data ) :
for pwm_params in pwm_data :
channel = pwm_params [ " channel " ]
2023-08-08 11:39:39 +08:00
with QSignalBlocker ( self . params [ channel ] ) :
2023-08-22 13:22:07 +08:00
self . params [ channel ] . child ( " Output Config " , " Limits " , " Max Absolute Voltage " ) . setValue ( pwm_params [ " max_v " ] [ " value " ] )
self . params [ channel ] . child ( " Output Config " , " Limits " , " Max Absolute Current " ) . setValue ( pwm_params [ " max_i_pos " ] [ " value " ] )
2023-07-20 13:49:23 +08:00
2023-08-01 13:32:06 +08:00
@pyqtSlot ( list )
def update_postfilter ( self , postfilter_data ) :
for postfilter_params in postfilter_data :
channel = postfilter_params [ " channel " ]
2023-08-08 11:39:39 +08:00
with QSignalBlocker ( self . params [ channel ] ) :
2023-08-29 12:39:32 +08:00
if postfilter_params [ " rate " ] == None :
self . params [ channel ] . child ( " Postfilter Config " , " Postfilter Rate " ) . setValue ( ( ' off ' , ) )
else :
self . params [ channel ] . child ( " Postfilter Config " , " Postfilter Rate " ) . setValue ( ( ' rate ' , postfilter_params [ " rate " ] ) )
2023-08-01 13:32:06 +08:00
2023-06-27 17:34:39 +08:00
async def coro_main ( ) :
2023-05-19 11:23:39 +08:00
args = get_argparser ( ) . parse_args ( )
if args . logLevel :
logging . basicConfig ( level = getattr ( logging , args . logLevel ) )
2023-06-27 17:34:39 +08:00
app_quit_event = asyncio . Event ( )
2023-06-26 10:20:48 +08:00
2023-06-27 17:34:39 +08:00
app = QtWidgets . QApplication . instance ( )
app . aboutToQuit . connect ( app_quit_event . set )
2023-06-26 10:20:48 +08:00
2023-07-01 23:46:40 +08:00
main_window = MainWindow ( args )
2023-05-19 11:23:39 +08:00
main_window . show ( )
2023-06-26 10:20:48 +08:00
2023-06-27 17:34:39 +08:00
await app_quit_event . wait ( )
def main ( ) :
qasync . run ( coro_main ( ) )
2023-05-19 11:23:39 +08:00
if __name__ == ' __main__ ' :
main ( )