new_gui: adopt backend-mounted sample on restart (status.sample sync)

Mount state was GUI-local, so a restart 'forgot' a physically-mounted sample.
_on_status now syncs from status.sample: adopt it when the GUI is empty or shows
a different sample (no robot mount), with a guard (_last_unmount_id) so a just-
unmounted sample isn't re-adopted from a stale poll. AppState.sync_mounted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
appleb_m
2026-06-26 10:55:08 +02:00
co-authored by Claude Opus 4.8
parent 14576216bd
commit 293e28f463
2 changed files with 40 additions and 0 deletions
+28
View File
@@ -89,6 +89,10 @@ class MainWindow(QWidget):
self._baton_poll.setInterval(1000)
self._baton_poll.timeout.connect(self._poll_baton_timeout)
# mounted-sample sync with the backend (status.sample)
self._mounted_db_id = None # what the GUI currently shows mounted
self._last_unmount_id = None # suppress re-adopting a just-unmounted one
# last payloads cached so a live theme rebuild can re-feed the new views
self._last_status = None
self._last_spreadsheet = None
@@ -231,6 +235,7 @@ class MainWindow(QWidget):
s = self.state
s.mode_changed.connect(self._on_mode_changed)
s.mount_requested.connect(self._on_mount_requested)
s.unmount_requested.connect(self._on_unmount_note)
s.unmount_requested.connect(self.daq.unmount)
self.daq.update.connect(self._on_status)
@@ -453,6 +458,26 @@ class MainWindow(QWidget):
if self._precondition_ok():
self.daq.fluorimeter_spectrum(req)
def _sync_mounted_from_status(self, s) -> None:
"""Reflect the backend's mounted sample (status.sample) in the GUI, so a
restart (or an external mount) doesn't 'forget' a mounted sample."""
bs = getattr(s, "sample", None)
if bs is None:
self._last_unmount_id = None # unmount confirmed by backend
return
bid = getattr(bs, "db_id", None)
if bid is not None and bid == self._last_unmount_id:
return # don't re-adopt a just-unmounted one
cur = self.state.mount_sample
cur_id = getattr(cur, "db_id", None) if cur is not None else None
if self.state.mount_phase != "mounted" or cur_id != bid:
self._mounted_db_id = bid
self.state.sync_mounted(bs)
def _on_unmount_note(self) -> None:
self._last_unmount_id = self._mounted_db_id
self._mounted_db_id = None
@Slot(object, bool)
def _on_mount_requested(self, sample, reference: bool = False) -> None:
reason = preconditions.hutch_blocks_mount(self.manual.last_status)
@@ -460,6 +485,8 @@ class MainWindow(QWidget):
QMessageBox.critical(self, "Mounting blocked", reason)
self.alert.show_message(reason, "error")
return
self._mounted_db_id = getattr(sample, "db_id", None)
self._last_unmount_id = None
self.daq.mount(sample, reference)
def _open_staff_tools(self) -> None:
@@ -530,6 +557,7 @@ class MainWindow(QWidget):
def _on_status(self, s) -> None:
self._last_status = s
self._auto_busy_edge(s)
self._sync_mounted_from_status(s)
session = getattr(s, "session", None)
staff = bool(getattr(session, "staff", False)) if session else False
if staff != self._staff:
+12
View File
@@ -216,6 +216,18 @@ class AppState(QObject):
self.mount_changed.emit()
self.pipe_changed.emit()
def sync_mounted(self, sample) -> None:
"""Adopt a sample the backend reports as mounted (e.g. on GUI restart).
Does NOT drive the robot — it only reflects reality. No mount_requested."""
self._mount_sample = sample
self._manual_mount = False
if self._mount_phase != "mounted":
self._reset_pipe()
self.pipe_changed.emit()
self._mount_phase = "mounted"
self.mount_changed.emit()
def unmount(self) -> None:
"""mounted -> empty: clear sample. Robot unmount only for DB samples."""
manual = getattr(self, "_manual_mount", False)