fix(bec_widgets): use cached device reads for sample-storage & slit polling
Both widgets' polling loops were issuing live EPICS round-trips every 2s even though the underlying signals are auto_monitor=True and BEC already keeps a fresh monitored value locally. Switching to read(cached=True) removes that redundant hardware traffic; for the slit widget this also makes the old round-robin "selected slit every tick, others in rotation" strategy unnecessary, so it now polls all six slits every tick.
This commit is contained in:
@@ -256,10 +256,16 @@ class SampleStorageWidget(BECWidget, QWidget):
|
||||
``flomni_samples_sample_in_gripper_name``), the same keys the CLI
|
||||
iterated over in ftransfer_sample_change. The raw name signal packs
|
||||
name+owner (see ``sample_desc_codec``), unpacked here.
|
||||
|
||||
Every underlying signal is an ``auto_monitor=True`` ``EpicsSignal``
|
||||
(see ``FlomniSampleStorage``), so BEC already keeps a fresh cached
|
||||
value per signal via CA monitor callbacks. We pass ``cached=True``
|
||||
so the poll returns those monitored values instead of issuing a
|
||||
live EPICS round-trip for all ~23 signals every 2 s.
|
||||
"""
|
||||
result: dict[int, tuple[bool, str, str]] = {}
|
||||
try:
|
||||
data = self._samples.read()
|
||||
data = self._samples.read(cached=True)
|
||||
except Exception as exc:
|
||||
logger.warning(f"SampleStorageWidget: bulk read failed: {exc}")
|
||||
# keep whatever is currently displayed rather than blanking out
|
||||
|
||||
@@ -130,7 +130,6 @@ class SlitControlWidget(BECWidget, QWidget):
|
||||
self.step = self._STEP_DEFAULT
|
||||
self._ov_items: dict[tuple[int, int], QTableWidgetItem] = {}
|
||||
self._shortcuts: list[QShortcut] = []
|
||||
self._slow_poll_idx: int = 0
|
||||
self._fetch_thread: Optional[threading.Thread] = None
|
||||
self._slit_data_ready.connect(self._apply_slit_data)
|
||||
self._locked: bool = False
|
||||
@@ -478,11 +477,17 @@ class SlitControlWidget(BECWidget, QWidget):
|
||||
sc.setEnabled(True)
|
||||
|
||||
def _populate_target_spins(self) -> None:
|
||||
"""Pre-fill target spinboxes with current device values."""
|
||||
"""Pre-fill target spinboxes with current device values.
|
||||
|
||||
Uses ``cached=True``: the motor's readback signal is
|
||||
``auto_monitor=True``, so BEC already holds a fresh value pushed via
|
||||
CA monitor and a live round-trip isn't needed for this display-only
|
||||
prefill.
|
||||
"""
|
||||
for suffix, spin in self._target_spins.items():
|
||||
try:
|
||||
device = getattr(self.dev, self._dev_name(suffix))
|
||||
reading = device.read()
|
||||
reading = device.read(cached=True)
|
||||
value = float(reading[self._dev_name(suffix)]["value"])
|
||||
spin.setValue(value)
|
||||
except Exception:
|
||||
@@ -527,14 +532,16 @@ class SlitControlWidget(BECWidget, QWidget):
|
||||
def _refresh_overview(self) -> None:
|
||||
"""
|
||||
Kick off a background thread to fetch slit values without blocking the
|
||||
Qt main thread. device.read() on virtual slit devices triggers RPC
|
||||
calls to the device server; doing those in the main thread stalls the UI.
|
||||
The thread emits _slit_data_ready when done; _apply_slit_data updates
|
||||
the table and target spinboxes safely from the main thread.
|
||||
Qt main thread. The thread emits _slit_data_ready when done;
|
||||
_apply_slit_data updates the table and target spinboxes safely from
|
||||
the main thread.
|
||||
|
||||
Per-tick strategy (2 s timer):
|
||||
- Selected slit: every tick.
|
||||
- Other slits: one per tick in rotation (~10 s per non-selected slit).
|
||||
The fetch uses ``cached=True`` (see ``_fetch_one_slit``): readback
|
||||
signals are ``auto_monitor=True``, so each read is a local lookup of
|
||||
BEC's monitor-updated cache rather than a live EPICS round-trip.
|
||||
That makes it cheap enough to read all six slits every 2 s tick —
|
||||
no need for the round-robin "selected slit every tick, others in
|
||||
rotation" strategy a live read would have required.
|
||||
"""
|
||||
if self._fetch_thread is not None and self._fetch_thread.is_alive():
|
||||
return # previous fetch still running – skip this tick
|
||||
@@ -544,21 +551,22 @@ class SlitControlWidget(BECWidget, QWidget):
|
||||
def _fetch_slit_data(self) -> None:
|
||||
"""Runs in background thread – NO Qt widget access here."""
|
||||
data: dict[tuple[int, str], Optional[float]] = {}
|
||||
self._fetch_one_slit(self.slit, data)
|
||||
others = [n for n in self._SLIT_LABELS if n != self.slit]
|
||||
if others:
|
||||
self._slow_poll_idx = self._slow_poll_idx % len(others)
|
||||
self._fetch_one_slit(others[self._slow_poll_idx], data)
|
||||
self._slow_poll_idx += 1
|
||||
for slit_num in self._SLIT_LABELS:
|
||||
self._fetch_one_slit(slit_num, data)
|
||||
self._slit_data_ready.emit(data) # thread-safe: Qt queues it to main thread
|
||||
|
||||
def _fetch_one_slit(self, slit_num: int, data: dict[tuple[int, str], Optional[float]]) -> None:
|
||||
"""Fetch four axis values for one slit into data (background thread)."""
|
||||
"""Fetch four axis values for one slit into data (background thread).
|
||||
|
||||
Uses ``cached=True`` — the motor readback signals are
|
||||
``auto_monitor=True``, so this display poll reads BEC's
|
||||
monitor-updated cache instead of issuing a live EPICS round-trip.
|
||||
"""
|
||||
for suffix in ("xc", "xs", "yc", "ys"):
|
||||
dev_name = f"sl{slit_num}{suffix}"
|
||||
try:
|
||||
device = getattr(self.dev, dev_name)
|
||||
reading = device.read()
|
||||
reading = device.read(cached=True)
|
||||
data[(slit_num, suffix)] = float(reading[dev_name]["value"])
|
||||
except Exception:
|
||||
data[(slit_num, suffix)] = None
|
||||
|
||||
Reference in New Issue
Block a user