diff --git a/client/brewpi_gui.py b/client/brewpi_gui.py index a93ed81..e4acc0f 100755 --- a/client/brewpi_gui.py +++ b/client/brewpi_gui.py @@ -291,10 +291,13 @@ 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. + # (anchor_min, T, Theta) from the server's simulation-based estimate + # of the remaining steps, recomputed on every step change - + # anchor_min is elapsed_min as of receipt, so on_plot_timer() can + # re-anchor the projection without it drifting right every tick. + # 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') @@ -411,20 +414,27 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow): if remembered_ambient is not None: self.msg_system.send({'AmbientTemp': remembered_ambient}) + def _elapsed_min(self): + """Simulated-schedule minutes elapsed since the run started, or + None if no run is in progress. While paused, uses the moment the + pause began as "now" so it freezes instead of drifting ahead of + actual progress.""" + if self.sud_start_time is None: + return None + now = self.sud_pause_started_at if self.sud_pause_started_at is not None else time.monotonic() + elapsed_real_s = now - self.sud_start_time - self.sud_paused_total + # The forecast's x-axis is simulated-schedule time; scale real + # elapsed time by the server's warp factor to match it. + return elapsed_real_s * self.warp_factor / 60.0 + def on_plot_timer(self): self.plot.sample( self.plot_temp_ist, self.plot_temp_soll, self.plot_rate_ist, self.plot_rate_soll, self.plot_power_set, self.plot_power_eff) - if self.sud_start_time is not None: - # While paused, use the moment the pause began as "now" so the - # line freezes instead of drifting ahead of actual progress. - now = self.sud_pause_started_at if self.sud_pause_started_at is not None else time.monotonic() - elapsed_real_s = now - self.sud_start_time - self.sud_paused_total - # The forecast's x-axis is simulated-schedule time; scale real - # elapsed time by the server's warp factor to match it. - elapsed_min = elapsed_real_s * self.warp_factor / 60.0 + elapsed_min = self._elapsed_min() + if elapsed_min is not None: self.forecast_plot.set_progress(elapsed_min) # Re-anchor the forecast from the live state instead of redrawing @@ -435,14 +445,21 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow): if self.sud_state != SUD_PAUSED_STATE and self.plot_temp_ist: self.forecast_history.append((elapsed_min, self.plot_temp_ist)) if self.server_remaining_forecast is not None: - t_rem, theta_rem = self.server_remaining_forecast + # Anchored to elapsed_min as of when the server computed + # this forecast, not the current tick's - it's only + # recomputed on step changes, so re-adding the live, + # ever-growing elapsed_min here would push the whole + # projection further right every tick until the next + # step change, instead of holding it fixed. + anchor_min, 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. + anchor_min = elapsed_min 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] + t_rem_min = [anchor_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() @@ -849,7 +866,13 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow): self.update_status_step_label() elif "RemainingForecast" in key: forecast = msg['RemainingForecast'] - self.server_remaining_forecast = (forecast['T'], forecast['Theta']) + # Anchor to elapsed_min as of receipt - close enough to + # when the server actually computed it (it's seeded from + # the live theta_ist at that moment) - rather than the + # live elapsed_min on_plot_timer() sees on later ticks. + anchor_min = self._elapsed_min() + if anchor_min is not None: + self.server_remaining_forecast = (anchor_min, forecast['T'], forecast['Theta']) elif "Forecast" in key: self.on_sud_forecast_received(msg['Forecast']) self.update_sud_actions()