Track and display energy consumption per Sud step

Each StepPlate on the Progress tab now shows a step's energy use in
Wh - live and growing while it's the active step, frozen at its final
total once finished, blank before it's ever been reached.

Integrated server-side from the heater's own live effective power
(pot.get_power(), already fed from heater.power_eff - see server/
brewpi.py's wiring), since actual energy used can't be predicted from
the forecast like the timing/temperature fields are, only measured as
it happens. Banked into energy_by_step on every genuine step
transition (on_step_changed(), keyed off the same index-change check
used for the ramp->hold phase switch), including the final transition
to DONE; reset on every fresh Load.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qvu5giu7gvRCyEWzf2Vpx
This commit is contained in:
2026-06-24 19:09:10 +02:00
co-authored by Claude Sonnet 4.6
parent 8896f216d1
commit 6e19162c16
2 changed files with 105 additions and 2 deletions
+36 -2
View File
@@ -301,7 +301,9 @@ class StepPlate(QtWidgets.QFrame):
self.label_masses = QtWidgets.QLabel()
self.label_rate = QtWidgets.QLabel()
self.label_stirrer = QtWidgets.QLabel()
for label in (self.label_target, self.label_actual, self.label_masses, self.label_rate, self.label_stirrer):
self.label_energy = QtWidgets.QLabel()
for label in (self.label_target, self.label_actual, self.label_masses, self.label_rate,
self.label_stirrer, self.label_energy):
label.setStyleSheet("color: #555;")
# A grid, not a single row - the Progress tab shares its pane's
@@ -315,13 +317,15 @@ class StepPlate(QtWidgets.QFrame):
layout.addWidget(self.label_remaining, 0, 2)
layout.addWidget(self.label_target, 1, 0, 1, 2)
layout.addWidget(self.label_actual, 1, 2)
layout.addWidget(self.label_masses, 2, 0, 1, 3)
layout.addWidget(self.label_masses, 2, 0, 1, 2)
layout.addWidget(self.label_energy, 2, 2)
layout.addWidget(self.label_rate, 3, 0, 1, 2)
layout.addWidget(self.label_stirrer, 3, 2)
self.set_step(step, resolved_temp)
self.set_actual_temp(None)
self.set_live_stirrer(None)
self.set_energy(None)
def set_step(self, step, resolved_temp):
"""Applies this step's static, schedule-config fields - called once,
@@ -345,6 +349,13 @@ class StepPlate(QtWidgets.QFrame):
def set_actual_temp(self, temp):
self.label_actual.setText("Actual {}".format("{:.1f} °C".format(temp) if temp is not None else ""))
def set_energy(self, wh):
"""wh is this step's energy consumption so far (Wh) - live and
still growing while this is the active step, frozen at its final
total once finished, or None before this step has ever been
reached (see Window._update_step_plates())."""
self.label_energy.setText("Energy {}".format("{:.0f} Wh".format(wh) if wh is not None else ""))
def set_live_stirrer(self, speed):
"""speed is the live rpm while this step is the active one, or None
to fall back to its configured (hold-phase) speed for a step
@@ -470,6 +481,16 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
# on_stirrer_changed()) so the Progress tab's active-step plate can
# show it.
self.stirrer_speed_ist = 0.0
# Energy consumption (Wh), straight from the latest 'Energy'
# message (see tasks/sud.py's SudTask._on_energy_changed()) -
# sud_energy_by_step covers every *finished* step (absolute index
# -> Wh), sud_energy_current is the running total for whichever
# step is presently active. Unlike the timing/temperature fields
# above, there's no forecast equivalent to preview before a run
# starts - energy actually used can only be measured, not
# predicted - so both stay empty/zero until one actually does.
self.sud_energy_by_step = {}
self.sud_energy_current = 0.0
self.msg_pot = self.msg_dispatch.msgio_get('Pot')
self.msg_sensor = self.msg_dispatch.msgio_get('Sensor')
self.msg_heater = self.msg_dispatch.msgio_get('Heater')
@@ -1097,6 +1118,10 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
self._update_step_plates()
elif "Forecast" in key:
self.on_sud_forecast_received(msg['Forecast'])
elif "Energy" in key:
self.sud_energy_by_step = dict(msg['Energy']['StepEnergy'])
self.sud_energy_current = msg['Energy']['Current']
self._update_step_plates()
elif "Error" in key:
# The server clears this back to None right after sending
# it (see tasks/sud.py's recv()) so it doesn't linger in
@@ -1203,6 +1228,8 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
self.sud_forecast_finished = False
self.sud_forecast_final_t = None
self.step_actual_frozen = {}
self.sud_energy_by_step = {}
self.sud_energy_current = 0.0
self._rebuild_step_plates()
self.update_sud_actions()
self.update_status_step_label()
@@ -1280,6 +1307,13 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
plate.set_actual_temp(None)
plate.set_live_stirrer(None)
if self.sud_step_index is not None and i == self.sud_step_index:
plate.set_energy(self.sud_energy_current)
elif i in self.sud_energy_by_step:
plate.set_energy(self.sud_energy_by_step[i])
else:
plate.set_energy(None)
def closeEvent(self, event):
print("Exitting gracefully!")
self.user_config.set('ambient_temperature', self.doubleSpinBox_ambient.value())