GUI: Thermostat Control Panel #147
@ -0,0 +1,36 @@
|
||||
from PyQt6 import QtWidgets
|
||||
from PyQt6.QtWidgets import QAbstractButton
|
||||
from PyQt6.QtCore import pyqtSignal, pyqtSlot
|
||||
|
||||
|
||||
class NetSettingsInputDiag(QtWidgets.QInputDialog):
|
||||
set_ipv4_act = pyqtSignal(str)
|
||||
|
||||
def __init__(self, current_ipv4_settings):
|
||||
super().__init__()
|
||||
self.setWindowTitle("Network Settings")
|
||||
self.setLabelText(
|
||||
"Set the Thermostat's IPv4 address, netmask and gateway (optional)"
|
||||
)
|
||||
self.setTextValue(current_ipv4_settings)
|
||||
self._new_ipv4 = ""
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_ipv4(ipv4_settings):
|
||||
self._new_ipv4 = ipv4_settings
|
||||
|
||||
sure = QtWidgets.QMessageBox(self)
|
||||
sure.setWindowTitle("Set network?")
|
||||
sure.setText(
|
||||
f"Setting this as network and disconnecting:<br>{ipv4_settings}"
|
||||
)
|
||||
|
||||
sure.buttonClicked.connect(self._emit_sig)
|
||||
sure.show()
|
||||
|
||||
self.textValueSelected.connect(set_ipv4)
|
||||
self.show()
|
||||
|
||||
@pyqtSlot(QAbstractButton)
|
||||
def _emit_sig(self, _):
|
||||
self.set_ipv4_act.emit(self._new_ipv4)
|
215
pythermostat/pythermostat/gui/view/thermostat_settings_menu.py
Normal file
215
pythermostat/pythermostat/gui/view/thermostat_settings_menu.py
Normal file
@ -0,0 +1,215 @@
|
||||
import logging
|
||||
from PyQt6 import QtWidgets, QtGui, QtCore
|
||||
from PyQt6.QtCore import pyqtSignal, pyqtSlot, QSignalBlocker
|
||||
from qasync import asyncSlot
|
||||
from pythermostat.gui.view.net_settings_input_diag import NetSettingsInputDiag
|
||||
from pythermostat.gui.model.thermostat import ThermostatConnectionState
|
||||
|
||||
|
||||
class ThermostatSettingsMenu(QtWidgets.QMenu):
|
||||
def __init__(self, thermostat, info_box, style):
|
||||
super().__init__()
|
||||
self._thermostat = thermostat
|
||||
self._info_box = info_box
|
||||
self._style = style
|
||||
self.setTitle("Thermostat settings")
|
||||
|
||||
self.hw_rev_data = dict()
|
||||
self._thermostat.hw_rev_update.connect(self.hw_rev)
|
||||
self._thermostat.connection_state_update.connect(
|
||||
self.thermostat_state_change_handler
|
||||
)
|
||||
|
||||
self.fan_group = QtWidgets.QWidget()
|
||||
self.fan_group.setEnabled(False)
|
||||
self.fan_group.setMinimumSize(QtCore.QSize(40, 0))
|
||||
self.fan_layout = QtWidgets.QHBoxLayout(self.fan_group)
|
||||
self.fan_layout.setSpacing(9)
|
||||
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))
|
||||
self.fan_layout.addWidget(self.fan_lbl)
|
||||
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))
|
||||
self.fan_power_slider.setRange(1, 100)
|
||||
self.fan_power_slider.setOrientation(QtCore.Qt.Orientation.Horizontal)
|
||||
self.fan_layout.addWidget(self.fan_power_slider)
|
||||
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))
|
||||
self.fan_layout.addWidget(self.fan_auto_box)
|
||||
self.fan_pwm_warning = QtWidgets.QLabel(parent=self.fan_group)
|
||||
self.fan_pwm_warning.setMinimumSize(QtCore.QSize(16, 0))
|
||||
self.fan_layout.addWidget(self.fan_pwm_warning)
|
||||
|
||||
self.fan_power_slider.valueChanged.connect(self.fan_set_request)
|
||||
self.fan_auto_box.stateChanged.connect(self.fan_auto_set_request)
|
||||
self._thermostat.fan_update.connect(self.fan_update)
|
||||
|
||||
self.fan_lbl.setToolTip("Adjust the fan")
|
||||
self.fan_lbl.setText("Fan:")
|
||||
self.fan_auto_box.setText("Auto")
|
||||
|
||||
fan = QtWidgets.QWidgetAction(self)
|
||||
fan.setDefaultWidget(self.fan_group)
|
||||
self.addAction(fan)
|
||||
self.fan = fan
|
||||
|
||||
self.actionReset = QtGui.QAction("Reset Thermostat", self)
|
||||
self.actionReset.triggered.connect(self.reset_request)
|
||||
self.addAction(self.actionReset)
|
||||
|
||||
self.actionEnter_DFU_Mode = QtGui.QAction("Enter DFU Mode", self)
|
||||
self.actionEnter_DFU_Mode.triggered.connect(self.dfu_request)
|
||||
self.addAction(self.actionEnter_DFU_Mode)
|
||||
|
||||
self.actionnet_settings_input_diag = QtGui.QAction("Set IPV4 Settings", self)
|
||||
self.actionnet_settings_input_diag.triggered.connect(self.net_settings_request)
|
||||
self.addAction(self.actionnet_settings_input_diag)
|
||||
|
||||
@asyncSlot(bool)
|
||||
async def load(_):
|
||||
await self._thermostat.load_cfg()
|
||||
|
||||
self._info_box.display_info_box(
|
||||
"Config loaded", "All channel configs have been loaded from flash."
|
||||
)
|
||||
|
||||
self.actionLoad_all_configs = QtGui.QAction("Load Config", self)
|
||||
self.actionLoad_all_configs.triggered.connect(load)
|
||||
self.addAction(self.actionLoad_all_configs)
|
||||
|
||||
@asyncSlot(bool)
|
||||
async def save(_):
|
||||
await self._thermostat.save_cfg()
|
||||
|
||||
self._info_box.display_info_box(
|
||||
"Config saved", "All channel configs have been saved to flash."
|
||||
)
|
||||
|
||||
self.actionSave_all_configs = QtGui.QAction("Save Config", self)
|
||||
self.actionSave_all_configs.triggered.connect(save)
|
||||
self.addAction(self.actionSave_all_configs)
|
||||
|
||||
def about_thermostat():
|
||||
QtWidgets.QMessageBox.about(
|
||||
self,
|
||||
"About Thermostat",
|
||||
f"""
|
||||
<h1>Sinara 8451 Thermostat v{self.hw_rev_data['rev']['major']}.{self.hw_rev_data['rev']['minor']}</h1>
|
||||
|
||||
<br>
|
||||
|
||||
<h2>Settings:</h2>
|
||||
Default fan curve:
|
||||
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']}
|
||||
<br>
|
||||
Fan PWM range:
|
||||
{self.hw_rev_data['settings']['min_fan_pwm']} \u2013 {self.hw_rev_data['settings']['max_fan_pwm']}
|
||||
<br>
|
||||
Fan PWM frequency: {self.hw_rev_data['settings']['fan_pwm_freq_hz']} Hz
|
||||
<br>
|
||||
Fan available: {self.hw_rev_data['settings']['fan_available']}
|
||||
<br>
|
||||
Fan PWM recommended: {self.hw_rev_data['settings']['fan_pwm_recommended']}
|
||||
""",
|
||||
)
|
||||
|
||||
self.actionAbout_Thermostat = QtGui.QAction("About Thermostat", self)
|
||||
self.actionAbout_Thermostat.triggered.connect(about_thermostat)
|
||||
self.addAction(self.actionAbout_Thermostat)
|
||||
|
||||
@pyqtSlot("QVariantMap")
|
||||
def fan_update(self, fan_settings):
|
||||
logging.debug(fan_settings)
|
||||
if fan_settings is None:
|
||||
return
|
||||
with QSignalBlocker(self.fan_power_slider):
|
||||
self.fan_power_slider.setValue(
|
||||
fan_settings["fan_pwm"] or 100 # 0 = PWM off = full strength
|
||||
)
|
||||
with QSignalBlocker(self.fan_auto_box):
|
||||
self.fan_auto_box.setChecked(fan_settings["auto_mode"])
|
||||
|
||||
def set_fan_pwm_warning(self):
|
||||
if self.fan_power_slider.value() != 100:
|
||||
pixmapi = getattr(QtWidgets.QStyle.StandardPixmap, "SP_MessageBoxWarning")
|
||||
icon = self._style.standardIcon(pixmapi)
|
||||
self.fan_pwm_warning.setPixmap(icon.pixmap(16, 16))
|
||||
self.fan_pwm_warning.setToolTip(
|
||||
"Throttling the fan (not recommended on this hardware rev)"
|
||||
)
|
||||
else:
|
||||
self.fan_pwm_warning.setPixmap(QtGui.QPixmap())
|
||||
self.fan_pwm_warning.setToolTip("")
|
||||
|
||||
@pyqtSlot(ThermostatConnectionState)
|
||||
def thermostat_state_change_handler(self, state):
|
||||
if state == ThermostatConnectionState.DISCONNECTED:
|
||||
self.fan_pwm_warning.setPixmap(QtGui.QPixmap())
|
||||
self.fan_pwm_warning.setToolTip("")
|
||||
|
||||
@pyqtSlot("QVariantMap")
|
||||
def hw_rev(self, hw_rev):
|
||||
self.hw_rev_data = hw_rev
|
||||
self.fan_group.setEnabled(self.hw_rev_data["settings"]["fan_available"])
|
||||
|
||||
@asyncSlot(int)
|
||||
async def fan_set_request(self, value):
|
||||
assert self._thermostat.connected()
|
||||
|
||||
if self.fan_auto_box.isChecked():
|
||||
with QSignalBlocker(self.fan_auto_box):
|
||||
self.fan_auto_box.setChecked(False)
|
||||
await self._thermostat.set_fan(value)
|
||||
if not self.hw_rev_data["settings"]["fan_pwm_recommended"]:
|
||||
self.set_fan_pwm_warning()
|
||||
|
||||
@asyncSlot(int)
|
||||
async def fan_auto_set_request(self, enabled):
|
||||
assert self._thermostat.connected()
|
||||
|
||||
if enabled:
|
||||
await self._thermostat.set_fan("auto")
|
||||
self.fan_update(await self._thermostat.get_fan())
|
||||
else:
|
||||
await self.thermostat.set_fan(
|
||||
self.fan_power_slider.value()
|
||||
)
|
||||
|
||||
@asyncSlot(bool)
|
||||
async def reset_request(self, _):
|
||||
assert self._thermostat.connected()
|
||||
|
||||
await self._thermostat.reset()
|
||||
await self._thermostat.end_session()
|
||||
self._thermostat.connection_state = ThermostatConnectionState.DISCONNECTED
|
||||
|
||||
@asyncSlot(bool)
|
||||
async def dfu_request(self, _):
|
||||
assert self._thermostat.connected()
|
||||
|
||||
await self._thermostat.dfu()
|
||||
await self._thermostat.end_session()
|
||||
self._thermostat.connection_state = ThermostatConnectionState.DISCONNECTED
|
||||
|
||||
@asyncSlot(bool)
|
||||
async def net_settings_request(self, _):
|
||||
assert self._thermostat.connected()
|
||||
|
||||
ipv4 = await self._thermostat.get_ipv4()
|
||||
self.net_settings_input_diag = NetSettingsInputDiag(ipv4["addr"])
|
||||
self.net_settings_input_diag.set_ipv4_act.connect(self.set_net_settings_request)
|
||||
|
||||
@asyncSlot(str)
|
||||
async def set_net_settings_request(self, ipv4_settings):
|
||||
assert self._thermostat.connected()
|
||||
|
||||
await self._thermostat.set_ipv4(ipv4_settings)
|
||||
await self._thermostat.end_session()
|
||||
self._thermostat.connection_state = ThermostatConnectionState.DISCONNECTED
|
@ -11,6 +11,7 @@ from qasync import asyncSlot, asyncClose
|
||||
from pythermostat.gui.model.thermostat import Thermostat, ThermostatConnectionState
|
||||
from pythermostat.gui.view.connection_details_menu import ConnectionDetailsMenu
|
||||
from pythermostat.gui.view.info_box import InfoBox
|
||||
from pythermostat.gui.view.thermostat_settings_menu import ThermostatSettingsMenu
|
||||
from pythermostat.gui.view.zero_limits_warning_view import ZeroLimitsWarningView
|
||||
|
||||
|
||||
@ -68,6 +69,11 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
)
|
||||
self.connect_btn.setMenu(self.connection_details_menu)
|
||||
|
||||
self._thermostat_settings_menu = ThermostatSettingsMenu(
|
||||
self._thermostat, self._info_box, self.style()
|
||||
)
|
||||
self.thermostat_settings.setMenu(self._thermostat_settings_menu)
|
||||
|
||||
# Status line
|
||||
self._zero_limits_warning_view = ZeroLimitsWarningView(
|
||||
self._thermostat, self.style(), self.limits_warning
|
||||
|
Loading…
Reference in New Issue
Block a user