Use the server's simulated forecast for the dynamic remainder too
SudForecastPlot.show_dynamic()'s dashed "remaining" line was still
using the naive abs(delta)/rate estimate even after a run started,
even though the (much more accurate) server-side
SudForecastEstimator was already available - the static forecast got
the upgrade, the live one didn't.
tasks/sud.py: SudTask now recomputes a forecast for just the remaining
steps (seeded from the live current temperature) on every step change
and sends it as {'RemainingForecast': {...}}. Building the synthetic
"rest of this step" entry for a step currently HOLDING needs to keep
the current step's grain_mass/water_mass/temperature/etc - a bare
{'hold': {...}} re-resolves those to None against the synthetic doc's
empty default.step, breaking derive_plant_params() (caught live: a
TypeError on every step change, silently swallowed by asyncio).
client/brewpi_gui.py: on_plot_timer() now uses the server's
RemainingForecast for the dashed projection whenever available,
falling back to the naive estimate only for the brief window before
each step's recomputation arrives. show_dynamic() takes the already-
computed (t_rem, theta_rem) instead of computing it internally, so the
GUI/server sources are interchangeable from its point of view.
This commit is contained in:
+30
-13
@@ -187,24 +187,21 @@ class SudForecastPlot(FigureCanvasQTAgg):
|
||||
self.figure.tight_layout()
|
||||
self.draw_idle()
|
||||
|
||||
def show_dynamic(self, history, remaining_schedule, theta_now, elapsed_min, name):
|
||||
def show_dynamic(self, history, t_rem_min, theta_rem, name):
|
||||
"""Re-anchored forecast for a run in progress: history is the
|
||||
[(t_min, theta), ...] actually measured so far (drawn solid),
|
||||
remaining_schedule the not-yet-done steps, projected forward
|
||||
(dashed) from (elapsed_min, theta_now) using the same nominal-rate
|
||||
estimate as show_schedule(), just re-seeded each call instead of
|
||||
computed once at t=0."""
|
||||
[(t_min, theta), ...] actually measured so far (drawn solid);
|
||||
t_rem_min/theta_rem is the projected remainder (dashed), already
|
||||
computed by the caller - either the server's simulation-based
|
||||
SudForecastEstimator (preferred, see Window.on_plot_timer()) or,
|
||||
until that arrives, a quick naive abs(delta)/rate fallback."""
|
||||
hist_t = [t for t, _ in history]
|
||||
hist_theta = [theta for _, theta in history]
|
||||
self.line.set_data(hist_t, hist_theta)
|
||||
|
||||
t_rem, theta_rem = self._estimate_course(remaining_schedule, theta_now)
|
||||
t_rem_min = [elapsed_min + seconds / 60.0 for seconds in t_rem]
|
||||
self.line_projected.set_data(t_rem_min, theta_rem)
|
||||
|
||||
self.ax.relim()
|
||||
self.ax.autoscale_view()
|
||||
total_min = t_rem_min[-1] if t_rem_min else elapsed_min
|
||||
total_min = t_rem_min[-1] if t_rem_min else (hist_t[-1] if hist_t else 0.0)
|
||||
self.ax.set_title("{} - {:.0f} min total (est.)".format(name, total_min), fontsize='small')
|
||||
self.figure.tight_layout()
|
||||
self.draw_idle()
|
||||
@@ -294,6 +291,11 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
# [(t_min, theta), ...] actually measured since the current run
|
||||
# started - the "already happened" part of the dynamic forecast.
|
||||
self.forecast_history = []
|
||||
# (T, Theta) from the server's simulation-based estimate of the
|
||||
# remaining steps, recomputed on every step change - None until it
|
||||
# arrives (or after a step change, until the next one does), in
|
||||
# which case on_plot_timer() falls back to the naive estimate.
|
||||
self.server_remaining_forecast = None
|
||||
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')
|
||||
@@ -432,9 +434,16 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
# the projection from correctly) - just leave the last draw up.
|
||||
if self.sud_state != SUD_PAUSED_STATE and self.plot_temp_ist:
|
||||
self.forecast_history.append((elapsed_min, self.plot_temp_ist))
|
||||
remaining = self._remaining_schedule()
|
||||
self.forecast_plot.show_dynamic(
|
||||
self.forecast_history, remaining, self.plot_temp_ist, elapsed_min, self.sud_name)
|
||||
if self.server_remaining_forecast is not None:
|
||||
t_rem, theta_rem = self.server_remaining_forecast
|
||||
else:
|
||||
# Server's simulation-based remainder hasn't arrived
|
||||
# yet (it's recomputed on every step change, off the
|
||||
# event loop) - fall back to the quick naive estimate
|
||||
# for this one tick.
|
||||
t_rem, theta_rem = self.forecast_plot._estimate_course(self._remaining_schedule(), self.plot_temp_ist)
|
||||
t_rem_min = [elapsed_min + seconds / 60.0 for seconds in t_rem]
|
||||
self.forecast_plot.show_dynamic(self.forecast_history, t_rem_min, theta_rem, self.sud_name)
|
||||
else:
|
||||
self.forecast_plot.clear_progress()
|
||||
|
||||
@@ -510,6 +519,7 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.sud_step_type = None
|
||||
self.sud_hold_remaining = 0.0
|
||||
self.forecast_history = []
|
||||
self.server_remaining_forecast = None
|
||||
self.set_tc_cooling(False)
|
||||
self.update_sud_actions()
|
||||
self.update_status_env_label()
|
||||
@@ -829,10 +839,17 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.sud_step_index = step.get('Index')
|
||||
self.sud_step_descr = step.get('Descr')
|
||||
self.sud_step_type = step.get('Type')
|
||||
# Stale for the step that just ended - the server is
|
||||
# already recomputing it (see on_plot_timer()'s fallback
|
||||
# to the naive estimate in the meantime).
|
||||
self.server_remaining_forecast = None
|
||||
self.update_status_step_label()
|
||||
elif "HoldRemaining" in key:
|
||||
self.sud_hold_remaining = msg['HoldRemaining']
|
||||
self.update_status_step_label()
|
||||
elif "RemainingForecast" in key:
|
||||
forecast = msg['RemainingForecast']
|
||||
self.server_remaining_forecast = (forecast['T'], forecast['Theta'])
|
||||
elif "Forecast" in key:
|
||||
self.on_sud_forecast_received(msg['Forecast'])
|
||||
self.update_sud_actions()
|
||||
|
||||
Reference in New Issue
Block a user