Add an enable/disable switch for the temperature controller

New TempControllerBase.enabled (default False) forces y=0 in
process_pid() when off, same mechanism as the existing cooling
override - the controller tries not to drive the heater at all.

tasks/tempctrl.py: new 'Enable' recv command and 'Enabled' broadcast
on the TempCtrl channel. tasks/sud.py: SudTask now enables the
controller on any non-IDLE/DONE Sud state and disables it (forcing
power back to 0) on IDLE/DONE, so automatic mode owns it for the
duration of a run and hands back manual control once it stops/finishes.

gui: new "Enabled" checkbox on the Manual tab's Controller group,
synced from the server; the temp/heatrate setpoint controls are
disabled whenever the controller itself is disabled.
This commit is contained in:
2026-06-21 12:54:09 +02:00
parent e2222dae5e
commit 3fea29cc58
6 changed files with 78 additions and 9 deletions
+20 -4
View File
@@ -517,11 +517,27 @@
<property name="title">
<string>Controller</string>
</property>
<widget class="QCheckBox" name="checkbox_tc_enable">
<property name="geometry">
<rect>
<x>0</x>
<y>25</y>
<width>141</width>
<height>25</height>
</rect>
</property>
<property name="toolTip">
<string>While disabled, the controller tries not to drive the heater (output forced to 0) and the setpoints below are inactive</string>
</property>
<property name="text">
<string>Enabled</string>
</property>
</widget>
<widget class="QSlider" name="Slider_temp_soll">
<property name="geometry">
<rect>
<x>20</x>
<y>70</y>
<y>100</y>
<width>63</width>
<height>281</height>
</rect>
@@ -540,7 +556,7 @@
<property name="geometry">
<rect>
<x>10</x>
<y>370</y>
<y>400</y>
<width>67</width>
<height>31</height>
</rect>
@@ -556,7 +572,7 @@
<property name="geometry">
<rect>
<x>80</x>
<y>370</y>
<y>400</y>
<width>51</width>
<height>31</height>
</rect>
@@ -575,7 +591,7 @@
<property name="geometry">
<rect>
<x>0</x>
<y>30</y>
<y>60</y>
<width>141</width>
<height>31</height>
</rect>
+24
View File
@@ -252,6 +252,9 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
self.sud_empty = True
self.sud_state = None
self.sud_user_message = None
# Mirrors the server's TempController.enabled - gates the
# temp/heatrate setpoint controls (see on_tempctrl_changed()).
self.tc_enabled = False
# Global, not Sud-specific - from the 'System' channel's one-time
# startup message. warp_factor defaults to 1.0 (no scaling) until
# that arrives, or for a server that doesn't send it at all.
@@ -295,6 +298,9 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
self.msg_system.set_recv_handler(self.on_system_changed)
self.setupUi(self)
# Matches tc_enabled's default (False) until the server says otherwise.
self.Slider_temp_soll.setEnabled(False)
self.doubleSpinBox_heatrate_soll.setEnabled(False)
# Permanent (right-aligned, not cleared by showMessage()/clearMessage())
# label for static environment info - distinct from the status bar's
# transient step/schedule messages.
@@ -329,6 +335,7 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
self.checkbox_heater_activate.stateChanged.connect(self.on_checkbox_changed)
self.checkbox_stirrer_activate.stateChanged.connect(self.on_checkbox_stirrer_activate_changed)
self.checkbox_tc_enable.stateChanged.connect(self.on_checkbox_tc_enable_changed)
self.lineEdit_ambient.returnPressed.connect(self.on_ambient_entry_changed)
self.btn_pot_reset.clicked.connect(self.on_action_pot_reset)
# Only meaningful (and only shown) once the server confirms its Pot
@@ -469,6 +476,12 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
self.sud_empty = True
self.sud_state = None
self.sud_user_message = None
self.tc_enabled = False
self.checkbox_tc_enable.blockSignals(True)
self.checkbox_tc_enable.setChecked(False)
self.checkbox_tc_enable.blockSignals(False)
self.Slider_temp_soll.setEnabled(False)
self.doubleSpinBox_heatrate_soll.setEnabled(False)
self.ambient_temp = None
self.lineEdit_ambient.clear()
self.btn_pot_reset.setVisible(False)
@@ -577,6 +590,10 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
print("on_checkbox_stirrer_activate_changed {}".format(value))
self.msg_stirrer.send({'Activate': int(value == 2)})
def on_checkbox_tc_enable_changed(self, value):
print("on_checkbox_tc_enable_changed {}".format(value))
self.msg_tempctrl.send({'Enable': value == 2})
def on_action_sud_start(self):
self.msg_sud.send({'Start': True})
@@ -661,6 +678,13 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
self.doubleSpinBox_heatrate_soll.blockSignals(True)
self.doubleSpinBox_heatrate_soll.setValue(subsubmsg['Set'])
self.doubleSpinBox_heatrate_soll.blockSignals(False)
elif "Enabled" in key:
self.tc_enabled = msg['Enabled']
self.checkbox_tc_enable.blockSignals(True)
self.checkbox_tc_enable.setChecked(self.tc_enabled)
self.checkbox_tc_enable.blockSignals(False)
self.Slider_temp_soll.setEnabled(self.tc_enabled)
self.doubleSpinBox_heatrate_soll.setEnabled(self.tc_enabled)
if "Ist" in key:
submsg = msg['Ist']
if 'Temp' in submsg:
+9 -4
View File
@@ -190,24 +190,27 @@ class Ui_MainWindow(object):
self.groupBox_2.setSizePolicy(sizePolicy)
self.groupBox_2.setMinimumSize(QtCore.QSize(0, 0))
self.groupBox_2.setObjectName("groupBox_2")
self.checkbox_tc_enable = QtWidgets.QCheckBox(self.groupBox_2)
self.checkbox_tc_enable.setGeometry(QtCore.QRect(0, 25, 141, 25))
self.checkbox_tc_enable.setObjectName("checkbox_tc_enable")
self.Slider_temp_soll = QtWidgets.QSlider(self.groupBox_2)
self.Slider_temp_soll.setGeometry(QtCore.QRect(20, 70, 63, 281))
self.Slider_temp_soll.setGeometry(QtCore.QRect(20, 100, 63, 281))
self.Slider_temp_soll.setMinimum(0)
self.Slider_temp_soll.setMaximum(100)
self.Slider_temp_soll.setOrientation(QtCore.Qt.Vertical)
self.Slider_temp_soll.setObjectName("Slider_temp_soll")
self.label_6 = QtWidgets.QLabel(self.groupBox_2)
self.label_6.setGeometry(QtCore.QRect(10, 370, 67, 31))
self.label_6.setGeometry(QtCore.QRect(10, 400, 67, 31))
self.label_6.setAlignment(QtCore.Qt.AlignCenter)
self.label_6.setObjectName("label_6")
self.doubleSpinBox_heatrate_soll = QtWidgets.QDoubleSpinBox(self.groupBox_2)
self.doubleSpinBox_heatrate_soll.setGeometry(QtCore.QRect(80, 370, 51, 31))
self.doubleSpinBox_heatrate_soll.setGeometry(QtCore.QRect(80, 400, 51, 31))
self.doubleSpinBox_heatrate_soll.setDecimals(1)
self.doubleSpinBox_heatrate_soll.setMaximum(3.0)
self.doubleSpinBox_heatrate_soll.setSingleStep(0.1)
self.doubleSpinBox_heatrate_soll.setObjectName("doubleSpinBox_heatrate_soll")
self.label_9 = QtWidgets.QLabel(self.groupBox_2)
self.label_9.setGeometry(QtCore.QRect(0, 30, 141, 31))
self.label_9.setGeometry(QtCore.QRect(0, 60, 141, 31))
self.label_9.setAlignment(QtCore.Qt.AlignCenter)
self.label_9.setObjectName("label_9")
self.horizontalLayout.addWidget(self.groupBox_2)
@@ -1288,6 +1291,8 @@ class Ui_MainWindow(object):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "BrewPi"))
self.groupBox_2.setTitle(_translate("MainWindow", "Controller"))
self.checkbox_tc_enable.setToolTip(_translate("MainWindow", "While disabled, the controller tries not to drive the heater (output forced to 0) and the setpoints below are inactive"))
self.checkbox_tc_enable.setText(_translate("MainWindow", "Enabled"))
self.label_6.setText(_translate("MainWindow", "Heat rate"))
self.label_9.setText(_translate("MainWindow", "Temperature [°C]"))
self.groupBox.setTitle(_translate("MainWindow", "Heater"))