From 56de5cf4d642982bbf52c50d173d9e2be3215b95 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Sun, 21 Jun 2026 00:38:31 +0200 Subject: [PATCH] Show ambient temperature and warp factor in the GUI's status bar Adds a 'System' channel for static, global info that doesn't belong to any one task - sent once at startup directly from brewpi.py's __main__ (a client connecting later still gets it via the dispatcher's global_state replay on subscribe, same mechanism SudTask's startup Name/Description already relies on). Moves warp_factor out of SudTask's startup message and into this new channel, since it's not actually Sud-specific - it's now available (and the status bar shows it) even without a Sud configured. The forecast plot's progress-line scaling switches to the same general Window.warp_factor field. Displayed via a permanent status bar widget (not cleared by the existing transient step/schedule showMessage() calls). --- brewpi/brewpi.py | 7 +++++++ client/brewpi_gui.py | 38 ++++++++++++++++++++++++++++++-------- tasks/sud.py | 7 +------ 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/brewpi/brewpi.py b/brewpi/brewpi.py index f1f827b..1050ea8 100755 --- a/brewpi/brewpi.py +++ b/brewpi/brewpi.py @@ -94,6 +94,13 @@ if __name__ == '__main__': pot.set_on_changed("temp", ChangedFloat(sensor.set_fake_temp, prec=3).set) sensor.set_fake_temp(pot.temp) + # Static, global info that doesn't belong to any one task - sent once; + # a client connecting later still gets it via the dispatcher's replay + # of accumulated global_state on subscribe. + async def send_system_info(): + await dispatcher.msgio_get("System").send({'AmbientTemp': theta_amb, 'WarpFactor': DT / DT_TASK}) + asyncio.ensure_future(send_system_info()) + # Message dispatcher h_dispatcher = taskmgr.start() h_server = server.listen("0.0.0.0", 8765) diff --git a/client/brewpi_gui.py b/client/brewpi_gui.py index 7b6b7b8..3c68981 100755 --- a/client/brewpi_gui.py +++ b/client/brewpi_gui.py @@ -190,10 +190,11 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow): self.sud_loaded = False self.sud_state = None self.sud_user_message = None - # Simulated-seconds-per-real-second, from SudTask's 'WarpFactor' - # message; defaults to 1.0 (no scaling) until that arrives, or for - # a server that doesn't send it at all. - self.sud_warp_factor = 1.0 + # Global, not Sud-specific - from the 'System' channel's one-time + # startup message. warp_factor defaults to 1.0 (no scaling) until + # that arrives, or for a server that doesn't send it at all. + self.ambient_temp = None + self.warp_factor = 1.0 # time.monotonic() of the last IDLE/DONE -> running transition, used # to position the forecast plot's progress line; None while not # running. sud_paused_total accumulates time spent PAUSED so the @@ -208,6 +209,7 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow): self.msg_stirrer = self.msg_dispatch.msgio_get('Stirrer') self.msg_tempctrl = self.msg_dispatch.msgio_get('TempCtrl') self.msg_sud = self.msg_dispatch.msgio_get('Sud') + self.msg_system = self.msg_dispatch.msgio_get('System') self.msg_pot.set_recv_handler(self.on_pot_changed) self.msg_sensor.set_recv_handler(self.on_sensor_changed) @@ -215,8 +217,15 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow): self.msg_stirrer.set_recv_handler(self.on_stirrer_changed) self.msg_tempctrl.set_recv_handler(self.on_tempctrl_changed) self.msg_sud.set_recv_handler(self.on_sud_changed) + self.msg_system.set_recv_handler(self.on_system_changed) self.setupUi(self) + # Permanent (right-aligned, not cleared by showMessage()/clearMessage()) + # label for static environment info - distinct from the status bar's + # transient step/schedule messages. + self.status_label_env = QtWidgets.QLabel() + self.statusBar().addPermanentWidget(self.status_label_env) + self.update_status_env_label() self.update_sud_actions() self.btn_connect.clicked.connect(self.on_btn_connect_clicked) self.actionStart.triggered.connect(self.on_action_sud_start) @@ -294,7 +303,7 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow): 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.sud_warp_factor / 60.0 + elapsed_min = elapsed_real_s * self.warp_factor / 60.0 self.forecast_plot.set_progress(elapsed_min) else: self.forecast_plot.clear_progress() @@ -321,11 +330,13 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow): self.sud_loaded = False self.sud_state = None self.sud_user_message = None - self.sud_warp_factor = 1.0 + self.ambient_temp = None + self.warp_factor = 1.0 self.sud_start_time = None self.sud_paused_total = 0.0 self.sud_pause_started_at = None self.update_sud_actions() + self.update_status_env_label() def update_sud_actions(self): running = self.sud_loaded and self.sud_state in SUD_RUNNING_STATES @@ -336,6 +347,19 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow): self.actionPause.setEnabled(running) self.actionStop.setEnabled(running or paused) + def on_system_changed(self, msg): + print("on_system_changed {}".format(msg)) + for key in msg: + if "AmbientTemp" in key: + self.ambient_temp = msg['AmbientTemp'] + elif "WarpFactor" in key: + self.warp_factor = msg['WarpFactor'] + self.update_status_env_label() + + def update_status_env_label(self): + ambient = "{:.1f}°C".format(self.ambient_temp) if self.ambient_temp is not None else "-" + self.status_label_env.setText("Ambient: {} Warp: {:.1f}x".format(ambient, self.warp_factor)) + def on_slider_temp_soll_changed(self, value): print("on_slider_temp_soll_changed {}".format(value)) self.msg_tempctrl.send({'Soll': {'Temp': value}}) @@ -491,8 +515,6 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow): self.update_sud_forecast(msg['Json']) elif "Name" in key: self.statusBar().showMessage("Sud schedule updated: {}".format(msg['Name']), 5000) - elif "WarpFactor" in key: - self.sud_warp_factor = msg['WarpFactor'] elif "State" in key: prev_state = self.sud_state self.sud_state = msg['State'] diff --git a/tasks/sud.py b/tasks/sud.py index f890356..00050ac 100644 --- a/tasks/sud.py +++ b/tasks/sud.py @@ -24,7 +24,6 @@ class SudTask(ATask): # its own, so hold_remaining must be ticked by dt explicitly to get # the same speedup instead of running in real time. self.dt = dt - self.warp_factor = dt / interval self.msg_handler = msg_handler msg_handler.set_recv_handler(self.recv) @@ -112,11 +111,7 @@ class SudTask(ATask): self.sud.set_on_changed('user_message', self.on_user_message_changed) self.sud.set_on_changed('hold_remaining', ChangedFloat(self.on_hold_remaining_changed, prec=0).set) - asyncio.create_task(self.send({ - 'Name': self.sud.name, - 'Description': self.sud.description, - 'WarpFactor': self.warp_factor, - })) + asyncio.create_task(self.send({'Name': self.sud.name, 'Description': self.sud.description})) while True: if self.sud.state == SudState.RAMPING: