Fix elapsed showing --:-- at DONE, and energy not reset on restart

sud_elapsed_seconds is intentionally reset to None once a run leaves
a running state (it gates the forecast plot's dynamic-vs-static view
switch) - but the status bar/Progress tab reused it too, so they lost
the final total exactly when it mattered most: right as the run
finished. Added sud_elapsed_last, which only ever gets overwritten by
a real 'Elapsed' push and is reset on a fresh Start/Load, for those
two displays to use instead.

Separately, a fresh Start (restarting an already-loaded, now IDLE/
DONE schedule) never sent a new Load, so SudTask's energy bookkeeping
- previously only reset in the Load handler - kept showing the
previous run's banked totals until each step was revisited. Now also
reset on a fresh Start (not a Pause->resume, which should keep it).

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:39:03 +02:00
co-authored by Claude Sonnet 4.6
parent 3c5b3053e9
commit 2913b41cab
2 changed files with 54 additions and 10 deletions
+46 -10
View File
@@ -438,6 +438,16 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
# load, which used to make the live trace visibly drift from the
# forecast.
self.sud_elapsed_seconds = None
# The last real 'Elapsed' value seen, full stop - unlike
# sud_elapsed_seconds above, never reset back to None once the run
# leaves a running state (that's specifically what the forecast
# plot's dynamic-vs-static view switch needs - see _elapsed_min()),
# so the status bar/Progress tab can still show where the run
# actually ended up rather than "--:--" the moment it finishes.
# Reset on a fresh Start/Load (see on_sud_changed()/
# update_sud_forecast()) - a new run's total shouldn't open by
# showing the previous one's.
self.sud_elapsed_last = None
self.sud_name = ''
self.sud_schedule = []
self.sud_pot_mass = 0
@@ -1039,6 +1049,19 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
if self.sud_state in SUD_RUNNING_STATES and self.sud_elapsed_seconds is None:
self.sud_elapsed_seconds = 0.0
self.sud_elapsed_last = 0.0
# A genuine fresh start (excludes Pause->resume, which
# never hits this branch - sud_elapsed_seconds is
# already non-None throughout a pause) means whatever
# energy the previous run banked belongs to that run,
# not this one - mirrors tasks/sud.py's SudTask.recv()
# resetting its own server-side bookkeeping on the same
# event. The server will overwrite these with the
# now-reset real figures within a tick or two regardless,
# this just avoids a stale flash of the old run's totals
# in between.
self.sud_energy_by_step = {}
self.sud_energy_current = 0.0
# Start the dynamic forecast's measured-history trace
# fresh for this run.
self.forecast_history = []
@@ -1093,6 +1116,13 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
self.sud_hold_remaining = msg['HoldRemaining']
self.update_status_step_label()
elif "Elapsed" in key:
# Unlike sud_elapsed_seconds below, kept regardless of
# dynamic-view mode - see its own comment for why - so the
# status bar/Progress tab still have the real final total
# once a run finishes rather than "--:--".
self.sud_elapsed_last = msg['Elapsed']
self._update_step_plates()
self.update_status_step_label()
# Sud.elapsed resets to 0 on every fresh Load too (not just
# on an actual run start - see components/sud.py's
# _reset_run_state()) and broadcasts unconditionally - only
@@ -1115,8 +1145,6 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
# at x=0.
if not self.forecast_history and self.plot_temp_ist and msg['Elapsed'] < FRESH_RUN_ELAPSED_THRESHOLD_S:
self.forecast_history.append((msg['Elapsed'] / 60.0, self.plot_temp_ist))
self._update_step_plates()
self.update_status_step_label()
elif "Forecast" in key:
self.on_sud_forecast_received(msg['Forecast'])
elif "Energy" in key:
@@ -1185,15 +1213,17 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
pot, water, grain, pot + water + grain)
text = text + " " + mass_text if text else mass_text
# Sums, not just the active step's own figures: total process time
# is just self.sud_elapsed_seconds (Sud's own tick-counted total -
# is just self.sud_elapsed_last (Sud's own tick-counted total -
# every step's time, including any WAIT_USER dwell, already adds
# up into it sequentially with no gaps); total energy has no such
# single running counter server-side (see tasks/sud.py's SudTask -
# energy is banked per step, not accumulated as one grand total),
# so it's summed here from every finished step's own Wh total
# plus whatever the currently active one has used so far.
# up into it sequentially with no gaps) - sud_elapsed_seconds
# itself would go back to "--:--" the moment a run finishes, see
# its own comment, which is exactly when this total matters most.
# Total energy has no such single running counter server-side
# (see tasks/sud.py's SudTask - energy is banked per step, not
# accumulated as one grand total), so it's summed here from every
# finished step's own Wh total plus the currently active one.
elapsed_text = "Elapsed {}".format(
_format_duration(self.sud_elapsed_seconds) if self.sud_elapsed_seconds is not None else "--:--")
_format_duration(self.sud_elapsed_last) if self.sud_elapsed_last is not None else "--:--")
total_energy_kwh = (sum(self.sud_energy_by_step.values()) + self.sud_energy_current) / 1000.0
energy_text = "Energy {:.2f} kWh".format(total_energy_kwh)
text = "{} {} {}".format(text, elapsed_text, energy_text)
@@ -1245,6 +1275,7 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
self.step_actual_frozen = {}
self.sud_energy_by_step = {}
self.sud_energy_current = 0.0
self.sud_elapsed_last = None
self._rebuild_step_plates()
self.update_sud_actions()
self.update_status_step_label()
@@ -1287,7 +1318,12 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
mash schedule is at most a handful of steps."""
if not self.step_plates:
return
current_elapsed = self.sud_elapsed_seconds if self.sud_elapsed_seconds is not None else 0.0
# sud_elapsed_last, not sud_elapsed_seconds - the latter resets to
# None the moment a run finishes (see its own comment), which
# would otherwise make every finished step's remaining time jump
# back to its full duration right when the run ends instead of
# staying at 0:00.
current_elapsed = self.sud_elapsed_last if self.sud_elapsed_last is not None else 0.0
starts = self.sud_forecast_step_starts
n = len(self.step_plates)
for i, plate in enumerate(self.step_plates):
+8
View File
@@ -430,6 +430,14 @@ class SudTask(ATask):
# whatever temperature existed back at Load (time passed,
# possibly manual heating in between).
fresh_start = self.sud.state in (SudState.IDLE, SudState.DONE)
if fresh_start:
# Belongs to the run that just ended (Stop, or running
# the schedule through to DONE), not the one about to
# begin - a Pause->resume (fresh_start False) keeps it,
# same as the forecast above.
self.energy_step_accum_j = 0.0
self.energy_by_step = {}
self._energy_index = None
self.sud.start()
if fresh_start:
asyncio.create_task(self.send_forecast(self.sud.save()))