feat: show status line earlier and count energy without a running Sud
Both clients' status line only showed pot mass/water/energy once a Sud schedule was loaded and running, leaving it blank/minimal at connect time and while idle. Show it as early as possible instead, falling back to config.json's Pot section (mass/water_mass/volumen) when no schedule is loaded yet; step description/remaining-time still only appear once a schedule is actually active. web/app.js: updateStatusLine() now mirrors updatePotVisualization()'s existing sudEmpty ? potConfig... : sud... pattern, and adds a Volume figure that was previously only used for the pot-fill visualization. client/brewpi_gui.py: same treatment, plus new sud_volumen tracking (the desktop GUI had no volume concept at all before - added by reading the doc's own pot.volumen with a config fallback, since Sud._parse_data() doesn't return it). The System message's 'Pot' key now also refreshes the status label immediately so it doesn't wait for some unrelated message to trigger a repaint. tasks/sud.py: energy was only accumulated outside IDLE/DONE, so a manually-driven heater with no Sud loaded/started never counted toward the total shown in either client. Now counts in every state except DONE - IDLE banks into a stable index -1 that's never shown per-step (Progress tab only covers the schedule's own indices) but is included in the status line's summed total. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M2ierBoxW3v7nUbDw3M2pE
This commit is contained in:
+48
-30
@@ -457,6 +457,7 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||||||
self.sud_pot_mass = 0
|
self.sud_pot_mass = 0
|
||||||
self.sud_grain_mass = 0
|
self.sud_grain_mass = 0
|
||||||
self.sud_water_mass = 0
|
self.sud_water_mass = 0
|
||||||
|
self.sud_volumen = 0
|
||||||
self.sud_step_index = None
|
self.sud_step_index = None
|
||||||
self.sud_step_descr = None
|
self.sud_step_descr = None
|
||||||
self.sud_step_type = None
|
self.sud_step_type = None
|
||||||
@@ -745,6 +746,7 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||||||
self.sud_pot_mass = 0
|
self.sud_pot_mass = 0
|
||||||
self.sud_grain_mass = 0
|
self.sud_grain_mass = 0
|
||||||
self.sud_water_mass = 0
|
self.sud_water_mass = 0
|
||||||
|
self.sud_volumen = 0
|
||||||
self.sud_step_index = None
|
self.sud_step_index = None
|
||||||
self.sud_step_descr = None
|
self.sud_step_descr = None
|
||||||
self.sud_step_type = None
|
self.sud_step_type = None
|
||||||
@@ -787,6 +789,8 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||||||
self.doubleSpinBox_pot_temp.setVisible(msg['PlantSim'])
|
self.doubleSpinBox_pot_temp.setVisible(msg['PlantSim'])
|
||||||
elif "Pot" in key:
|
elif "Pot" in key:
|
||||||
self.pot_config = msg['Pot']
|
self.pot_config = msg['Pot']
|
||||||
|
if self.sud_empty:
|
||||||
|
self.update_status_step_label()
|
||||||
self.update_status_env_label()
|
self.update_status_env_label()
|
||||||
|
|
||||||
def on_action_pot_reset(self):
|
def on_action_pot_reset(self):
|
||||||
@@ -1166,42 +1170,50 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||||||
self._update_step_plates()
|
self._update_step_plates()
|
||||||
|
|
||||||
def update_status_step_label(self):
|
def update_status_step_label(self):
|
||||||
if self.sud_step_descr:
|
text = ""
|
||||||
# +1: sud_step_index is the 0-based index Sud.index uses, but
|
# Before any schedule is loaded, fall back to config.json's Pot
|
||||||
# StepPlate (the Progress tab) labels steps 1-based - match it
|
# section (self.pot_config, captured in on_system_changed()) so the
|
||||||
# here so the status bar doesn't announce a step one number
|
# status bar already shows pot mass/water/volume/energy as soon as
|
||||||
# below what the Progress tab shows for the same step.
|
# the server connects, rather than a blank status bar until Load -
|
||||||
text = "Step {}: {}".format(self.sud_step_index + 1, self.sud_step_descr)
|
# mirrors web/app.js's updateStatusLine(). Step description/
|
||||||
if self.sud_step_type == 'hold':
|
# remaining-time only exist once a schedule is actually loaded, so
|
||||||
remaining = max(self.sud_hold_remaining, 0.0)
|
# they stay inside the "not empty" branch below.
|
||||||
text += " ({:.0f}:{:02.0f} remaining)".format(remaining // 60, remaining % 60)
|
if self.sud_empty:
|
||||||
# sud_step_index is the same 0-based index Sud.index uses into
|
pot = self.pot_config.get('mass', 0)
|
||||||
# its own schedule list (see components/sud.py) - self.
|
water = self.pot_config.get('water_mass', 0)
|
||||||
# sud_schedule is built from the very same doc, so it lines up
|
volumen = self.pot_config.get('volumen', 0)
|
||||||
# directly; grain_mass/water_mass are already resolved against
|
grain = 0
|
||||||
# default.step there (see components/sud.py's _build_step()).
|
else:
|
||||||
if 0 <= self.sud_step_index < len(self.sud_schedule):
|
pot = self.sud_pot_mass
|
||||||
step = self.sud_schedule[self.sud_step_index]
|
volumen = self.sud_volumen
|
||||||
step_pot = step.get('pot', {})
|
grain, water = self.sud_grain_mass, self.sud_water_mass
|
||||||
grain, water = step_pot.get('grain_mass', 0), step_pot.get('water_mass', 0)
|
if self.sud_step_descr:
|
||||||
else:
|
# +1: sud_step_index is the 0-based index Sud.index uses, but
|
||||||
grain, water = self.sud_grain_mass, self.sud_water_mass
|
# StepPlate (the Progress tab) labels steps 1-based - match it
|
||||||
elif self.sud_schedule:
|
# here so the status bar doesn't announce a step one number
|
||||||
# Loaded but not started yet (no real Step message has
|
# below what the Progress tab shows for the same step.
|
||||||
|
text = "Step {}: {}".format(self.sud_step_index + 1, self.sud_step_descr)
|
||||||
|
if self.sud_step_type == 'hold':
|
||||||
|
remaining = max(self.sud_hold_remaining, 0.0)
|
||||||
|
text += " ({:.0f}:{:02.0f} remaining)".format(remaining // 60, remaining % 60)
|
||||||
|
# sud_step_index is the same 0-based index Sud.index uses into
|
||||||
|
# its own schedule list (see components/sud.py) - self.
|
||||||
|
# sud_schedule is built from the very same doc, so it lines up
|
||||||
|
# directly; grain_mass/water_mass are already resolved against
|
||||||
|
# default.step there (see components/sud.py's _build_step()).
|
||||||
|
if 0 <= self.sud_step_index < len(self.sud_schedule):
|
||||||
|
step = self.sud_schedule[self.sud_step_index]
|
||||||
|
step_pot = step.get('pot', {})
|
||||||
|
grain, water = step_pot.get('grain_mass', 0), step_pot.get('water_mass', 0)
|
||||||
|
# else: loaded but not started yet (no real Step message has
|
||||||
# arrived) - preview the doc's own initial grain_mass/
|
# arrived) - preview the doc's own initial grain_mass/
|
||||||
# water_mass right away instead of leaving the status line
|
# water_mass right away instead of leaving the status line
|
||||||
# blank until Start, mirroring tasks/sud.py's SudTask.
|
# blank until Start, mirroring tasks/sud.py's SudTask.
|
||||||
# apply_plant_params() applying it to the real plant/
|
# apply_plant_params() applying it to the real plant/
|
||||||
# controller immediately on Load too.
|
# controller immediately on Load too.
|
||||||
text = ""
|
|
||||||
grain, water = self.sud_grain_mass, self.sud_water_mass
|
|
||||||
else:
|
|
||||||
self.statusBar().clearMessage()
|
|
||||||
return
|
|
||||||
|
|
||||||
pot = self.sud_pot_mass
|
mass_text = "Pot: {:.2f} kg, Water: {:.2f} kg, Grain {:.2f} kg, total {:.2f} kg, Volume {:.1f} L".format(
|
||||||
mass_text = "Pot: {:.2f} kg, Water: {:.2f} kg, Grain {:.2f} kg, total {:.2f} kg".format(
|
pot, water, grain, pot + water + grain, volumen)
|
||||||
pot, water, grain, pot + water + grain)
|
|
||||||
text = text + " " + mass_text if text else mass_text
|
text = text + " " + mass_text if text else mass_text
|
||||||
# Sums, not just the active step's own figures: total process time
|
# Sums, not just the active step's own figures: total process time
|
||||||
# is just self.sud_elapsed_last (Sud's own tick-counted total -
|
# is just self.sud_elapsed_last (Sud's own tick-counted total -
|
||||||
@@ -1251,6 +1263,12 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||||||
self.sud_pot_mass = pot_mass
|
self.sud_pot_mass = pot_mass
|
||||||
self.sud_grain_mass = grain_mass
|
self.sud_grain_mass = grain_mass
|
||||||
self.sud_water_mass = water_mass
|
self.sud_water_mass = water_mass
|
||||||
|
# Sud._parse_data() has no notion of 'volumen' (unlike mass/L/Td,
|
||||||
|
# nothing downstream needs it for the actual physics) - read it
|
||||||
|
# straight off the doc, falling back to config.json's Pot.volumen
|
||||||
|
# same as _parse_data() does for mass, mirroring web/app.js's
|
||||||
|
# parseSudDoc().
|
||||||
|
self.sud_volumen = doc.get('pot', {}).get('volumen', self.pot_config.get('volumen', 0))
|
||||||
self.sud_empty = len(schedule) == 0
|
self.sud_empty = len(schedule) == 0
|
||||||
if is_new_schedule:
|
if is_new_schedule:
|
||||||
self.sud_step_descr = None
|
self.sud_step_descr = None
|
||||||
|
|||||||
+10
-5
@@ -653,11 +653,16 @@ class SudTask(ATask):
|
|||||||
# wiring), in Watts; dt is this tick's simulated seconds, so
|
# wiring), in Watts; dt is this tick's simulated seconds, so
|
||||||
# the product is Joules, accumulated for whichever step is
|
# the product is Joules, accumulated for whichever step is
|
||||||
# current (see on_step_changed()). Counted through every
|
# current (see on_step_changed()). Counted through every
|
||||||
# non-idle state, WAIT_USER/PAUSED included - the controller
|
# state except DONE, IDLE included - a manually-driven heater
|
||||||
# stays enabled (see on_state_changed()) and may well still
|
# (no schedule loaded/started yet, e.g. pre-heating strike
|
||||||
# be actively holding, drawing real power, even though the
|
# water by hand) still draws real power and should show up in
|
||||||
# schedule itself isn't progressing.
|
# the same total; self.sud.index is a stable -1 while IDLE
|
||||||
if self.sud.state not in (SudState.IDLE, SudState.DONE):
|
# (see components/sud.py's _reset_run_state()), so this banks
|
||||||
|
# into energy_by_step[-1] same as any other step once a real
|
||||||
|
# Start moves the index on - never shown per-step (Progress
|
||||||
|
# tab's step-plates only cover the schedule's own indices) but
|
||||||
|
# still included in the status line's summed total.
|
||||||
|
if self.sud.state != SudState.DONE:
|
||||||
self.energy_step_accum_j += self.pot.get_power() * self.dt
|
self.energy_step_accum_j += self.pot.get_power() * self.dt
|
||||||
self._energy_changed.set(self.energy_step_accum_j / 3600.0)
|
self._energy_changed.set(self.energy_step_accum_j / 3600.0)
|
||||||
self.sud.tick(self.dt)
|
self.sud.tick(self.dt)
|
||||||
|
|||||||
+30
-22
@@ -479,31 +479,39 @@ function updatePotVisualization() {
|
|||||||
|
|
||||||
function updateStatusLine() {
|
function updateStatusLine() {
|
||||||
const el = document.getElementById('sud-status-line');
|
const el = document.getElementById('sud-status-line');
|
||||||
if (sudSchedule.length === 0) {
|
|
||||||
const w = potConfig.water_mass || 0;
|
|
||||||
el.textContent = w > 0 ? `No schedule – ${w} L water` : '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let text = '';
|
let text = '';
|
||||||
let grain = sudGrainMass, water = sudWaterMass;
|
// Before any schedule is loaded, fall back to config.json's Pot
|
||||||
if (sudStepDescr) {
|
// section (via potConfig) so the status line already shows pot mass/
|
||||||
// +1: sudStepIndex is the 0-based index Sud.index uses, but the
|
// water/volume/energy as soon as the System message arrives - same
|
||||||
// Progress tab's step-plates (createStepPlate()) label 1-based -
|
// sudEmpty ? potConfig... : sud... pattern as updatePotVisualization()
|
||||||
// match it here, same fix as client/brewpi_gui.py's
|
// above. Step description/remaining-time only exist once a schedule
|
||||||
// update_status_step_label().
|
// is actually loaded, so they stay inside the sudEmpty===false branch.
|
||||||
text = `Step ${sudStepIndex + 1}: ${sudStepDescr}`;
|
let grain = 0, water = potConfig.water_mass || 0;
|
||||||
if (sudStepType === 'hold') {
|
let pot = potConfig.mass || 0;
|
||||||
const remaining = Math.max(sudHoldRemaining, 0);
|
let volumen = potConfig.volumen || 0;
|
||||||
text += ` (${Math.floor(remaining / 60)}:${String(Math.floor(remaining % 60)).padStart(2, '0')} remaining)`;
|
if (!sudEmpty) {
|
||||||
}
|
grain = sudGrainMass;
|
||||||
if (sudStepIndex !== null && sudStepIndex >= 0 && sudStepIndex < sudSchedule.length) {
|
water = sudWaterMass;
|
||||||
const step = sudSchedule[sudStepIndex];
|
pot = sudPotMass;
|
||||||
grain = step.grain_mass || 0;
|
volumen = sudVolumen;
|
||||||
water = step.water_mass || 0;
|
if (sudStepDescr) {
|
||||||
|
// +1: sudStepIndex is the 0-based index Sud.index uses, but the
|
||||||
|
// Progress tab's step-plates (createStepPlate()) label 1-based -
|
||||||
|
// match it here, same fix as client/brewpi_gui.py's
|
||||||
|
// update_status_step_label().
|
||||||
|
text = `Step ${sudStepIndex + 1}: ${sudStepDescr}`;
|
||||||
|
if (sudStepType === 'hold') {
|
||||||
|
const remaining = Math.max(sudHoldRemaining, 0);
|
||||||
|
text += ` (${Math.floor(remaining / 60)}:${String(Math.floor(remaining % 60)).padStart(2, '0')} remaining)`;
|
||||||
|
}
|
||||||
|
if (sudStepIndex !== null && sudStepIndex >= 0 && sudStepIndex < sudSchedule.length) {
|
||||||
|
const step = sudSchedule[sudStepIndex];
|
||||||
|
grain = step.grain_mass || 0;
|
||||||
|
water = step.water_mass || 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const pot = sudPotMass;
|
const massText = `Pot: ${pot.toFixed(2)} kg, Water: ${water.toFixed(2)} kg, Grain ${grain.toFixed(2)} kg, total ${(pot + water + grain).toFixed(2)} kg, Volume ${volumen.toFixed(1)} L`;
|
||||||
const massText = `Pot: ${pot.toFixed(2)} kg, Water: ${water.toFixed(2)} kg, Grain ${grain.toFixed(2)} kg, total ${(pot + water + grain).toFixed(2)} kg`;
|
|
||||||
text = text ? `${text} ${massText}` : massText;
|
text = text ? `${text} ${massText}` : massText;
|
||||||
const elapsedText = `Elapsed ${elapsed !== null ? formatDuration(elapsed) : '-:--:--'}`;
|
const elapsedText = `Elapsed ${elapsed !== null ? formatDuration(elapsed) : '-:--:--'}`;
|
||||||
const totalEnergyKwh = (Object.values(energyByStep).reduce((a, b) => a + b, 0) + energyCurrent) / 1000.0;
|
const totalEnergyKwh = (Object.values(energyByStep).reduce((a, b) => a + b, 0) + energyCurrent) / 1000.0;
|
||||||
|
|||||||
Reference in New Issue
Block a user