fix: send light slider changes from every input path, guard readback
CI / lint (push) Skipped
CI / test (3.11) (push) Skipped
CI / test (3.12) (push) Skipped
CI / test (3.13) (push) Skipped
CI / test-with-beamline-plugins (pxi_bec) (push) Skipped
CI / test-with-beamline-plugins (pxii_bec) (push) Skipped
CI / test-with-beamline-plugins (pxiii_bec) (push) Skipped
CI / lint (push) Skipped
CI / test (3.11) (push) Skipped
CI / test (3.12) (push) Skipped
CI / test (3.13) (push) Skipped
CI / test-with-beamline-plugins (pxi_bec) (push) Skipped
CI / test-with-beamline-plugins (pxii_bec) (push) Skipped
CI / test-with-beamline-plugins (pxiii_bec) (push) Skipped
Sliders only emitted on sliderReleased, so right-click+wheel, arrow keys and groove clicks changed the value locally without a PUT and the 500 ms status poll snapped it back. setTracking(False) + valueChanged now sends one PUT per deliberate change; readback setValue is wrapped in blockSignals to avoid echoing PUTs, and a 1.5 s grace window stops in-flight stale status responses from bouncing the slider. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,15 @@
|
||||
import time
|
||||
|
||||
from aarecommon.models.models import DAQStatusModel
|
||||
from PySide6.QtCore import Qt, Signal, Slot
|
||||
from PySide6.QtWidgets import QGridLayout, QLabel, QSlider, QWidget
|
||||
|
||||
from aare.gui.widgets.title_label import TitleLabel
|
||||
|
||||
# Skip readback updates this long after a user change: a status response already
|
||||
# in flight when the PUT was queued would otherwise snap the slider back.
|
||||
READBACK_GRACE_S = 1.5
|
||||
|
||||
|
||||
class IlluminationPanel(QWidget):
|
||||
front_light = Signal(int)
|
||||
@@ -21,11 +27,18 @@ class IlluminationPanel(QWidget):
|
||||
front_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
grid_layout.addWidget(front_label, 1, 0, 1, 2)
|
||||
self.is_sliding = False
|
||||
self._last_user_change = 0.0
|
||||
|
||||
self.front_light_slider = QSlider(orientation=Qt.Orientation.Horizontal, parent=self)
|
||||
self.front_light_slider.setRange(0, 100)
|
||||
# tracking off: valueChanged fires once per deliberate change (wheel notch,
|
||||
# key step, groove page-step, drag release). sliderReleased alone missed
|
||||
# every path except a handle drag, so wheel adjustments were never sent
|
||||
# and the 500 ms status poll reverted them.
|
||||
self.front_light_slider.setTracking(False)
|
||||
self.front_light_slider.sliderPressed.connect(self.on_slider_pressed)
|
||||
self.front_light_slider.sliderReleased.connect(self.on_front_slider_released)
|
||||
self.front_light_slider.sliderReleased.connect(self.on_slider_released)
|
||||
self.front_light_slider.valueChanged.connect(self.on_front_value_changed)
|
||||
grid_layout.addWidget(self.front_light_slider, 2, 0, 1, 2)
|
||||
|
||||
back_label = QLabel("Back light", parent=self)
|
||||
@@ -34,8 +47,10 @@ class IlluminationPanel(QWidget):
|
||||
|
||||
self.back_light_slider = QSlider(orientation=Qt.Orientation.Horizontal, parent=self)
|
||||
self.back_light_slider.setRange(0, 100)
|
||||
self.back_light_slider.setTracking(False)
|
||||
self.back_light_slider.sliderPressed.connect(self.on_slider_pressed)
|
||||
self.back_light_slider.sliderReleased.connect(self.on_back_slider_released)
|
||||
self.back_light_slider.sliderReleased.connect(self.on_slider_released)
|
||||
self.back_light_slider.valueChanged.connect(self.on_back_value_changed)
|
||||
grid_layout.addWidget(self.back_light_slider, 4, 0, 1, 2)
|
||||
|
||||
@Slot()
|
||||
@@ -43,17 +58,76 @@ class IlluminationPanel(QWidget):
|
||||
self.is_sliding = True
|
||||
|
||||
@Slot()
|
||||
def on_front_slider_released(self):
|
||||
def on_slider_released(self):
|
||||
self.is_sliding = False
|
||||
self.front_light.emit(self.front_light_slider.value())
|
||||
|
||||
@Slot()
|
||||
def on_back_slider_released(self):
|
||||
self.is_sliding = False
|
||||
self.back_light.emit(self.back_light_slider.value())
|
||||
@Slot(int)
|
||||
def on_front_value_changed(self, v: int):
|
||||
self._last_user_change = time.monotonic()
|
||||
self.front_light.emit(v)
|
||||
|
||||
@Slot(int)
|
||||
def on_back_value_changed(self, v: int):
|
||||
self._last_user_change = time.monotonic()
|
||||
self.back_light.emit(v)
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
if not self.is_sliding: # Update only if not sliding
|
||||
self.front_light_slider.setValue(round(s.bl.front_light))
|
||||
self.back_light_slider.setValue(round(s.bl.back_light))
|
||||
if self.is_sliding or time.monotonic() - self._last_user_change < READBACK_GRACE_S:
|
||||
return
|
||||
# blockSignals: readback must not loop back into valueChanged and echo
|
||||
# a PUT to the server every poll.
|
||||
for slider, val in (
|
||||
(self.front_light_slider, s.bl.front_light),
|
||||
(self.back_light_slider, s.bl.back_light),
|
||||
):
|
||||
slider.blockSignals(True)
|
||||
slider.setValue(round(val))
|
||||
slider.blockSignals(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# ponytail: smallest check that fails if the send/readback wiring breaks
|
||||
import os
|
||||
from types import SimpleNamespace
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
from PySide6.QtCore import QPoint, QPointF
|
||||
from PySide6.QtGui import QWheelEvent
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
app = QApplication([])
|
||||
panel = IlluminationPanel()
|
||||
sent = []
|
||||
panel.front_light.connect(sent.append)
|
||||
|
||||
# wheel notch must emit (was silently discarded before)
|
||||
panel.front_light_slider.setFocus()
|
||||
QApplication.sendEvent(
|
||||
panel.front_light_slider,
|
||||
QWheelEvent(
|
||||
QPointF(5, 5),
|
||||
QPointF(5, 5),
|
||||
QPoint(0, 0),
|
||||
QPoint(0, 120),
|
||||
Qt.MouseButton.RightButton,
|
||||
Qt.KeyboardModifier.NoModifier,
|
||||
Qt.ScrollPhase.NoScrollPhase,
|
||||
False,
|
||||
),
|
||||
)
|
||||
assert sent, "wheel adjustment must emit front_light"
|
||||
|
||||
# readback right after a user change must not clobber the slider
|
||||
held = panel.front_light_slider.value()
|
||||
status = SimpleNamespace(bl=SimpleNamespace(front_light=90.0, back_light=10.0))
|
||||
panel.update_daq_status(status)
|
||||
assert panel.front_light_slider.value() == held, "grace window must hold user value"
|
||||
|
||||
# after the grace window, readback applies without emitting a PUT
|
||||
panel._last_user_change = time.monotonic() - READBACK_GRACE_S - 1
|
||||
n_sent = len(sent)
|
||||
panel.update_daq_status(status)
|
||||
assert panel.front_light_slider.value() == 90, "readback must apply after grace"
|
||||
assert len(sent) == n_sent, "readback must not echo a PUT"
|
||||
print("gude")
|
||||
|
||||
Reference in New Issue
Block a user