PyThermostat GUI: Implement ThermostatSettingsMenu
Co-authored-by: linuswck <linuswck@m-labs.hk> Co-authored-by: Egor Savkin <es@m-labs.hk>
This commit is contained in:
parent
dd22470a30
commit
1b6939ee30
@ -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.model.thermostat import Thermostat, ThermostatConnectionState
|
||||||
from pythermostat.gui.view.connection_details_menu import ConnectionDetailsMenu
|
from pythermostat.gui.view.connection_details_menu import ConnectionDetailsMenu
|
||||||
from pythermostat.gui.view.info_box import InfoBox
|
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
|
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.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
|
# Status line
|
||||||
self._zero_limits_warning_view = ZeroLimitsWarningView(
|
self._zero_limits_warning_view = ZeroLimitsWarningView(
|
||||||
self._thermostat, self.style(), self.limits_warning
|
self._thermostat, self.style(), self.limits_warning
|
||||||
|
Loading…
Reference in New Issue
Block a user