From d3c6c52abf2d8b7b3d940d1583f34a41f7749715 Mon Sep 17 00:00:00 2001 From: David Perl Date: Tue, 20 Oct 2026 10:34:19 +0200 Subject: [PATCH 1/5] fix: sleep more if transmission set not finished --- src/aare/daq/daq.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/aare/daq/daq.py b/src/aare/daq/daq.py index a99b60c2..0331f815 100644 --- a/src/aare/daq/daq.py +++ b/src/aare/daq/daq.py @@ -1978,6 +1978,9 @@ class AareDAQ: # on completion correctly logger.debug("Sleeping to ensure transmission set completes due to IOC issues") time.sleep(2) + if self._devs.transmission == 0: + logger.warning("Transmission move still not completed, sleeping another 2s") + time.sleep(2) start_pos = getattr(request, "start", None) if start_pos is not None: -- 2.54.0 From c9d9d716ab99c3f8cbb4a0bcbc0d1099de67c113 Mon Sep 17 00:00:00 2001 From: David Perl Date: Tue, 20 Oct 2026 10:43:25 +0200 Subject: [PATCH 2/5] fix: remove idle timeout on gui causing more problems than it fixed --- src/aare/gui/main_window.py | 20 -------- tests/unit/gui/test_main_window.py | 79 ------------------------------ 2 files changed, 99 deletions(-) diff --git a/src/aare/gui/main_window.py b/src/aare/gui/main_window.py index 80b37c96..729e5e70 100644 --- a/src/aare/gui/main_window.py +++ b/src/aare/gui/main_window.py @@ -876,11 +876,6 @@ class MainWindow(QMainWindow): for locked_banner in self._locked_beamline_banners: locked_banner.installEventFilter(self) - self._idle_timer = QTimer(self) - self._idle_timer.setInterval(60_000) - self._idle_timer.timeout.connect(self._check_idle_timeout) - self._idle_timer.start() - self._remote_close_timer = QTimer(self) self._remote_close_timer.setInterval(1000) self._remote_close_timer.timeout.connect(self._check_remote_close_deadline) @@ -3190,18 +3185,3 @@ class MainWindow(QMainWindow): logger.warning("Closing GUI due to remote close request.") self.close() - - @Slot() - def _check_idle_timeout(self) -> None: - idle_for_s = time.time() - self._last_user_interaction_ts - if idle_for_s < self._idle_close_timeout_s: - return - - if not self._can_close_for_idle_or_remote(): - logger.info( - "Idle timeout reached, but GUI remains open because beamline is busy or automation is active." - ) - return - # TODO deny communication from GUI to DAQ while IDLE for too long rather than kill the GUI - logger.warning("Closing GUI after inactivity timeout.") - self.close() diff --git a/tests/unit/gui/test_main_window.py b/tests/unit/gui/test_main_window.py index c6cba4c0..67d04901 100644 --- a/tests/unit/gui/test_main_window.py +++ b/tests/unit/gui/test_main_window.py @@ -352,85 +352,6 @@ def test_automation_activity_refreshes_idle_timestamp(qtbot, mock_ui_state): assert win._remote_close_deadline_ts is None -def test_idle_timeout_closes_when_inactive_and_not_running(qtbot, mock_ui_state): - with ( - patch("requests.get"), - patch("aare.gui.main_window.DAQWorker"), - patch("aare.gui.main_window.PredictionSubscriber"), - patch("aare.gui.main_window.VideoThread"), - patch("aare.gui.main_window.JFJochDBusClient"), - patch("aare.gui.main_window.jwt.decode") as mock_jwt, - ): - mock_jwt.return_value = { - "sub": "testuser", - "staff": True, - "pgroups": ["p123"], - "session": 15, - } - fake_token = "header.payload.signature" - - win = MainWindow( - base_url=None, - token=fake_token, - default_image=None, - zmq_addr=None, - pred_zmq_addr=None, - beamline_cam_addr=None, - gonio_cam_addr=None, - gonio_cam_id=None, - ) - qtbot.addWidget(win) - - win._idle_close_timeout_s = 10 - win._last_user_interaction_ts = 100.0 - win.close = MagicMock() - - with patch("aare.gui.main_window.time.time", return_value=111.0): - win._check_idle_timeout() - - win.close.assert_called_once() - - -def test_idle_timeout_does_not_close_while_automation_active(qtbot, mock_ui_state): - with ( - patch("requests.get"), - patch("aare.gui.main_window.DAQWorker"), - patch("aare.gui.main_window.PredictionSubscriber"), - patch("aare.gui.main_window.VideoThread"), - patch("aare.gui.main_window.JFJochDBusClient"), - patch("aare.gui.main_window.jwt.decode") as mock_jwt, - ): - mock_jwt.return_value = { - "sub": "testuser", - "staff": True, - "pgroups": ["p123"], - "session": 15, - } - fake_token = "header.payload.signature" - - win = MainWindow( - base_url=None, - token=fake_token, - default_image=None, - zmq_addr=None, - pred_zmq_addr=None, - beamline_cam_addr=None, - gonio_cam_addr=None, - gonio_cam_id=None, - ) - qtbot.addWidget(win) - - win._idle_close_timeout_s = 10 - win._last_user_interaction_ts = 100.0 - win.job_list_panel.is_running = MagicMock(return_value=True) - win.close = MagicMock() - - with patch("aare.gui.main_window.time.time", return_value=111.0): - win._check_idle_timeout() - - win.close.assert_not_called() - - def _make_window(qtbot): win = MainWindow( base_url=None, -- 2.54.0 From 40096ef2e4290dac909513ccf6a6e03a02ca5494 Mon Sep 17 00:00:00 2001 From: David Perl Date: Tue, 20 Oct 2026 10:58:55 +0200 Subject: [PATCH 3/5] wip fix: don't restart on beam recovery --- src/aare/gui/panels/sample_queue_panel.py | 8 ++--- src/aare/gui/widgets/message_box.py | 36 +++-------------------- tests/unit/gui/test_message_box.py | 14 --------- 3 files changed, 8 insertions(+), 50 deletions(-) diff --git a/src/aare/gui/panels/sample_queue_panel.py b/src/aare/gui/panels/sample_queue_panel.py index b7b0ee00..1fa6a9b9 100644 --- a/src/aare/gui/panels/sample_queue_panel.py +++ b/src/aare/gui/panels/sample_queue_panel.py @@ -110,8 +110,8 @@ class SampleQueuePanel(QFrame): self.pause_on_conditions_cb.setChecked(True) self.pause_on_conditions_cb.setToolTip( "When on, automation refuses to start and pauses between samples if the beam, " - "experiment shutter, hutch doors or robot are not ready, auto-resuming when they " - "recover. Uncheck for testing to run straight through." + "experiment shutter, hutch doors or robot are not ready. Uncheck for testing " + "to run straight through." ) button_layout.addWidget(self.play_button) @@ -426,8 +426,8 @@ class SampleQueuePanel(QFrame): logger.warning(f"Automation paused; beamline not ready: {bad}") msg = "Automation paused — beamline not ready:\n- " + "\n- ".join(bad) - if conditions_auto_check(self, msg, self._conditions_ok): - logger.debug("Conditions recovered or user chose to continue; resuming automation") + if conditions_auto_check(self, msg): + logger.debug("User chose to continue; resuming automation") self.table_model.set_running(True) self._pause = False self.play_button.setText("⏸ Pause") diff --git a/src/aare/gui/widgets/message_box.py b/src/aare/gui/widgets/message_box.py index 87c38fac..ad90bed4 100644 --- a/src/aare/gui/widgets/message_box.py +++ b/src/aare/gui/widgets/message_box.py @@ -89,40 +89,12 @@ def reply_box(parent, title: str = "Warning", msg: str = "Warning."): ) -def timer_box( - parent, title: str = "Warning", msg: str = "Warning.", condition_func=None -) -> QMessageBox: +def continue_msg_box(parent, title: str = "Warning", msg: str = "Warning.") -> QMessageBox: box = QMessageBox(parent) box.setWindowTitle(title) box.setText(f"{msg} Do you wish to continue?") box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) box.show() - - timer = QTimer(box) - timer.setInterval(300) - - def stop_timer(): - if timer.isActive(): - timer.stop() - - def check(): - if not box.isVisible(): - stop_timer() - return - try: - if condition_func and condition_func(): - # Auto-accept (as if user pressed Yes) - box.done(QMessageBox.StandardButton.Yes) - stop_timer() - except Exception: - # Never let errors in condition_func block UI - logger.exception("Error in condition_func") - - box.accepted.connect(stop_timer) - box.rejected.connect(stop_timer) - timer.timeout.connect(check) - - timer.start() return box @@ -163,10 +135,10 @@ def ring_current_auto_check(parent, ring_current, check_func) -> bool: if ring_current is None else f"Ring current is low {round(ring_current, 2)} mA." ) - return conditions_auto_check(parent, msg, check_func, title="Ring current too low") + return conditions_auto_check(parent, msg, title="Ring current too low") -def conditions_auto_check(parent, msg: str, check_func, title: str = "Beamline not ready") -> bool: +def conditions_auto_check(parent, msg: str, title: str = "Beamline not ready") -> bool: """Pause-and-wait dialog that auto-resumes when ``check_func()`` becomes True. Used to hold automation between samples until the beamline recovers. The @@ -174,7 +146,7 @@ def conditions_auto_check(parent, msg: str, check_func, title: str = "Beamline n can also click 'Yes' to continue now (override) or 'No' to stop automation. Returns True to resume, False to stop. """ - box = timer_box(parent, title=title, msg=msg, condition_func=check_func) + box = continue_msg_box(parent, title=title, msg=msg) loop = QEventLoop() def finish(_=None): diff --git a/tests/unit/gui/test_message_box.py b/tests/unit/gui/test_message_box.py index 5ed7923f..0d440b9e 100644 --- a/tests/unit/gui/test_message_box.py +++ b/tests/unit/gui/test_message_box.py @@ -8,7 +8,6 @@ from aare.gui.widgets.message_box import ( reply_box, ring_current_auto_check, ring_current_low_check, - timer_box, ) @@ -21,19 +20,6 @@ def test_reply_box(qtbot): mock_question.assert_called_once() -def test_timer_box_auto_accept(qtbot): - parent = None - condition_func = MagicMock(return_value=True) - - # We need to process events for timer to fire - box = timer_box(parent, condition_func=condition_func) - - # Wait until box is closed by check() - qtbot.waitUntil(lambda: not box.isVisible(), timeout=2000) - - assert box.result() == QMessageBox.StandardButton.Yes - - @pytest.mark.timeout(10) def test_ring_current_low_check_ok(qtbot): # Should return True immediately if current is high enough -- 2.54.0 From a33189e738be90bc578cdd1badb1a2b82f76b57b Mon Sep 17 00:00:00 2001 From: David Perl Date: Tue, 20 Oct 2026 11:20:45 +0200 Subject: [PATCH 4/5] fix: import auto_exposure from correct file --- src/aare/beamline_dispatch/x10sa/beamline_dispatch.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/aare/beamline_dispatch/x10sa/beamline_dispatch.py b/src/aare/beamline_dispatch/x10sa/beamline_dispatch.py index e759557d..f0bd85bb 100644 --- a/src/aare/beamline_dispatch/x10sa/beamline_dispatch.py +++ b/src/aare/beamline_dispatch/x10sa/beamline_dispatch.py @@ -20,17 +20,17 @@ class X10SaBecMacros(BecMacros): save_and_reload, save_current_position, ) - from pxii_bec.macros.flux_macros import ( - bpm2flux, # pyright: ignore[reportMissingImports, reportMissingModuleSource] - ) - from pxii_bec.macros.katscripts import ( # pyright: ignore[reportMissingImports, reportMissingModuleSource] - auto_exposure, + from pxii_bec.macros.flux_macros import ( # pyright: ignore[reportMissingImports, reportMissingModuleSource] + bpm2flux, ) from pxii_bec.macros.pxii_energy import ( # pyright: ignore[reportMissingImports, reportMissingModuleSource] bl_energy, get_current_energy, mono_pitch_scan, ) + from pxii_bec.macros.pxii_setup import ( # pyright: ignore[reportMissingImports, reportMissingModuleSource] + auto_exposure, + ) from pxii_bec.scripts.init_beamline import ( # pyright: ignore[reportMissingImports, reportMissingModuleSource] init_beamline_environment, ) -- 2.54.0 From 379f439f35d5b6cf9a4520d7bbfe9def7d4fda0b Mon Sep 17 00:00:00 2001 From: David Perl Date: Tue, 20 Oct 2026 11:26:25 +0200 Subject: [PATCH 5/5] fix: propagate config through daq --- scripts/demo_scan_ingest.py | 21 --------------------- src/aare/daq/daq.py | 2 +- src/aare/daq/devices.py | 13 ------------- src/aare/daq/workflows.py | 3 ++- 4 files changed, 3 insertions(+), 36 deletions(-) delete mode 100644 scripts/demo_scan_ingest.py diff --git a/scripts/demo_scan_ingest.py b/scripts/demo_scan_ingest.py deleted file mode 100644 index 742fac66..00000000 --- a/scripts/demo_scan_ingest.py +++ /dev/null @@ -1,21 +0,0 @@ -from aarecommon.config.beamline import mx_beamline - -from aare.daq.aaredb import AareWrapper -from aare.daq.config import BeamlineConfig -from aare.daq.daq import AareDAQ -from aare.daq.devices import BeamlineDevices -from aare.devices.jfjoch import JFJochWrapper - -bl = mx_beamline() -j = JFJochWrapper(bl) -a = AareWrapper(bl) -c = BeamlineConfig(bl) -d = BeamlineDevices(bl) -daq = AareDAQ(bl=bl, cfg=c) -result = j.wait_till_done(60) -a.ingest_scan( - sample=c.current_sample, - result=result, - geom=daq.sample_geometry, - beam_mark_pxl=c.get_beam_mark(d.zoom), -) diff --git a/src/aare/daq/daq.py b/src/aare/daq/daq.py index 0331f815..7c587159 100644 --- a/src/aare/daq/daq.py +++ b/src/aare/daq/daq.py @@ -291,7 +291,7 @@ class AareDAQ: self.last_time = 0.0 self._dispatch = dispatch self._cfg = cfg - self._devs = BeamlineDevices(bl, dispatch) + self._devs = BeamlineDevices(bl, dispatch, cfg) self._mlbox = MlBox(bl) self._jfjoch = JFJochWrapper(bl) self._bl = bl.value.upper() diff --git a/src/aare/daq/devices.py b/src/aare/daq/devices.py index 2f465604..dbc645f5 100644 --- a/src/aare/daq/devices.py +++ b/src/aare/daq/devices.py @@ -375,16 +375,3 @@ class BeamlineDevices: def read_current_state_from_bec(self) -> BeamlineStateEnum: return self.bec_worker.read_current_state() - - -if __name__ == "__main__": - from aarecommon.config.beamline import mx_beamline - - beamline = mx_beamline() - devs = BeamlineDevices(beamline) - devs.bec_worker.scilog_msg( - message="Testing DAQ hijack of BEC sci log communication channel", color="blue" - ) - # devs.aerotech_pos = Coordinate(x=124.0, y=1.0, z=1.0) - # print(devs.aerotech_pos) - # devs.aerotech_omega = 0.0 diff --git a/src/aare/daq/workflows.py b/src/aare/daq/workflows.py index 41cd0c48..441d974c 100644 --- a/src/aare/daq/workflows.py +++ b/src/aare/daq/workflows.py @@ -3,6 +3,7 @@ import time from aarecommon.config.logger import setup_logger from aarecommon.models.models import SampleCameraSettings +from aare.beamline_dispatch.beamline_dispatch import get_beamline_dispatch from aare.daq.config import ABR_OMEGA_MOUNT, ABR_POS_MOUNT, BeamlineConfig from aare.daq.devices import BeamlineDevices from aare.devices.area_detector import AutoEnum @@ -284,7 +285,7 @@ if __name__ == "__main__": beamline = mx_beamline() config = BeamlineConfig(beamline) - devices = BeamlineDevices(beamline) + devices = BeamlineDevices(beamline, get_beamline_dispatch(), config) common_2rse(devices, config) time.sleep(10.0) rse2sa(devices, config) -- 2.54.0