fix(gui): wait for hw lock before auto-centering after mount #243
No files matched your search
@@ -1024,6 +1024,7 @@ class MainWindow(QMainWindow):
|
||||
)
|
||||
|
||||
self.job_list_panel.auto_scan.connect(self.daq.automated_scan)
|
||||
self.job_list_panel.auto_scan.connect(self.data_collection.auto_scan_started)
|
||||
|
||||
self.job_list_panel.unmount.connect(self.daq.unmount)
|
||||
self.job_list_panel.unmount_then_park_and_dry.connect(self.daq.unmount_then_park_and_dry)
|
||||
@@ -1148,6 +1149,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self.daq.automated_scan_done.connect(self.job_list_panel.automated_scan_done)
|
||||
self.daq.automated_scan_done.connect(self._mark_scan_result)
|
||||
self.daq.automated_scan_done.connect(self.data_collection.auto_scan_finished)
|
||||
self.daq.automation_critical_failure.connect(self._on_automation_critical_failure)
|
||||
self.daq.manual_collection_critical_failure.connect(
|
||||
self._on_manual_collection_critical_failure
|
||||
|
||||
@@ -195,6 +195,9 @@ class DataCollectionSettings(QFrame):
|
||||
# startup must not trigger an auto-centering (hardware would move
|
||||
# uninvited on every restart).
|
||||
self._status_seen = False
|
||||
# Set on mount, fired once the hw lock frees (see _track_sample).
|
||||
self._center_after_mount_pending = False
|
||||
self._auto_scans_in_flight = 0
|
||||
|
||||
self._tab_bar.currentChanged.connect(self._stack.setCurrentIndex)
|
||||
self._tab_bar.currentChanged.connect(self._on_tab_changed)
|
||||
@@ -242,23 +245,48 @@ class DataCollectionSettings(QFrame):
|
||||
self.raster.update_daq_status(s)
|
||||
self.screening.update_daq_status(s)
|
||||
self.simple.update_daq_status(s)
|
||||
self._track_sample(None if s.sample is None else s.sample.db_id)
|
||||
self._track_sample(None if s.sample is None else s.sample.db_id, s.busy)
|
||||
|
||||
def _track_sample(self, db_id: int | None):
|
||||
def _track_sample(self, db_id: int | None, busy: bool = False):
|
||||
# Separate from update_daq_status so tests can drive mount detection
|
||||
# without building a full DAQStatusModel.
|
||||
if db_id is None:
|
||||
# Forget on unmount so remounting the same sample counts as a
|
||||
# fresh mount below.
|
||||
self._sample_id = None
|
||||
self._center_after_mount_pending = False
|
||||
elif db_id != self._sample_id:
|
||||
self._sample_id = db_id
|
||||
self._tab_bar.setCurrentIndex(0)
|
||||
if self._status_seen and self.auto_center_after_mount.isChecked():
|
||||
# Reuse the button wiring (clicked -> daq.center_loop in
|
||||
# main_window) instead of a second signal path.
|
||||
self.find_tip.click()
|
||||
self._center_after_mount_pending = (
|
||||
self._status_seen
|
||||
and self.auto_center_after_mount.isChecked()
|
||||
and not self._auto_scans_in_flight
|
||||
)
|
||||
self._status_seen = True
|
||||
# The server publishes the new sample while the mount still holds the
|
||||
# hw lock, and /alc/center_loop refuses (non-blocking lock) while busy,
|
||||
# so firing on the id change alone was silently rejected. Wait for idle.
|
||||
if self._center_after_mount_pending and not busy:
|
||||
self._center_after_mount_pending = False
|
||||
# Reuse the button wiring (clicked -> daq.center_loop in
|
||||
# main_window) instead of a second signal path.
|
||||
self.find_tip.click()
|
||||
|
||||
# Queue automation centers each sample itself and frees the hw lock
|
||||
# between samples; a GUI centering in that gap could steal the lock from
|
||||
# the next /scan/auto. Tracks the request, not the queue's running flag,
|
||||
# because Pause flips that flag while the current sample is still running.
|
||||
# A counter, since the queue sends the next scan before the settings panel
|
||||
# sees the previous one's done signal.
|
||||
@Slot()
|
||||
def auto_scan_started(self):
|
||||
self._auto_scans_in_flight += 1
|
||||
self._center_after_mount_pending = False
|
||||
|
||||
@Slot()
|
||||
def auto_scan_finished(self):
|
||||
self._auto_scans_in_flight = max(0, self._auto_scans_in_flight - 1)
|
||||
|
||||
@Slot(int)
|
||||
def _on_tab_changed(self, idx: int):
|
||||
|
||||
@@ -353,6 +353,59 @@ def test_auto_center_fires_on_mount_only_when_armed(settings_panel):
|
||||
assert len(clicks) == 3
|
||||
|
||||
|
||||
def test_auto_center_waits_for_mount_to_release_hw_lock(settings_panel):
|
||||
# Server shows the new sample while the mount still holds the hw lock;
|
||||
# center_loop would be refused then, so the click must wait for idle.
|
||||
clicks = []
|
||||
settings_panel.find_tip.clicked.connect(lambda: clicks.append(1))
|
||||
settings_panel.auto_center_after_mount.setChecked(True)
|
||||
settings_panel._track_sample(1)
|
||||
settings_panel._track_sample(None)
|
||||
|
||||
settings_panel._track_sample(2, busy=True)
|
||||
settings_panel._track_sample(2, busy=True)
|
||||
assert not clicks
|
||||
settings_panel._track_sample(2, busy=False)
|
||||
assert len(clicks) == 1
|
||||
settings_panel._track_sample(2, busy=False)
|
||||
assert len(clicks) == 1
|
||||
|
||||
# Unmount before the lock frees drops the pending centering.
|
||||
settings_panel._track_sample(3, busy=True)
|
||||
settings_panel._track_sample(None, busy=False)
|
||||
assert len(clicks) == 1
|
||||
|
||||
|
||||
def test_auto_center_skipped_while_auto_scan_in_flight(settings_panel):
|
||||
# Automation centers itself and frees the lock between samples; a GUI
|
||||
# centering there could steal the lock from the next /scan/auto.
|
||||
clicks = []
|
||||
settings_panel.find_tip.clicked.connect(lambda: clicks.append(1))
|
||||
settings_panel.auto_center_after_mount.setChecked(True)
|
||||
settings_panel._track_sample(1)
|
||||
|
||||
# Queue sends the next scan before the settings panel sees the previous one
|
||||
# finish (signal order in main_window): must still count as in flight.
|
||||
settings_panel.auto_scan_started()
|
||||
settings_panel.auto_scan_started()
|
||||
settings_panel.auto_scan_finished()
|
||||
settings_panel._track_sample(2, busy=True)
|
||||
settings_panel._track_sample(2, busy=False)
|
||||
assert not clicks
|
||||
|
||||
# Pause mid-sample: the current scan still runs to completion, so no
|
||||
# centering until its request is done and a later manual mount happens.
|
||||
settings_panel._track_sample(3, busy=True)
|
||||
settings_panel.auto_scan_finished()
|
||||
settings_panel._track_sample(3, busy=False)
|
||||
assert not clicks
|
||||
|
||||
settings_panel._track_sample(None)
|
||||
settings_panel._track_sample(4, busy=True)
|
||||
settings_panel._track_sample(4, busy=False)
|
||||
assert len(clicks) == 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pending color while typing (movestate property, committed on Enter/focus-out)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user