fix(stop_button): enhance emergency stop functionality and update button behavior

This commit is contained in:
2026-07-01 16:16:51 +02:00
committed by Jan Wyzula
parent 80725f620e
commit 49855b5698
2 changed files with 82 additions and 11 deletions
@@ -1,5 +1,5 @@
from bec_qthemes import material_icon
from qtpy.QtCore import Qt
from qtpy.QtCore import Qt, QTimer
from qtpy.QtWidgets import QHBoxLayout, QPushButton, QSizePolicy, QToolButton, QWidget
from bec_widgets.utils.bec_widget import BECWidget
@@ -12,6 +12,9 @@ class StopButton(BECWidget, QWidget):
PLUGIN = True
ICON_NAME = "dangerous"
RPC = False
ABORT_LABEL = "Stop"
EMERGENCY_STOP_LABEL = "Emergency Stop"
EMERGENCY_STOP_TIMEOUT_MS = 2000
def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False, **kwargs):
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
@@ -22,15 +25,19 @@ class StopButton(BECWidget, QWidget):
self.layout.setSpacing(0)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setAlignment(Qt.AlignmentFlag.AlignVCenter)
self._emergency_stop_active = False
self._reset_timer = QTimer(self)
self._reset_timer.setSingleShot(True)
self._reset_timer.timeout.connect(self._deactivate_emergency_stop)
if toolbar:
icon = material_icon("stop", color="#cc181e", filled=True, convert_to_pixmap=False)
self.button = QToolButton(icon=icon)
self.button.setToolTip("Stop the scan queue")
self.button.setToolTip(self.ABORT_LABEL)
else:
self.button = QPushButton()
self.button.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
self.button.setText("Stop")
self.button.setText(self.ABORT_LABEL)
self.button.setProperty("variant", "danger")
self.button.clicked.connect(self.stop_scan)
@@ -41,12 +48,39 @@ class StopButton(BECWidget, QWidget):
self,
): # , scan_id: str | None = None): #FIXME scan_id will be added when combining with Queue widget
"""
Stop the scan.
Abort the scan by default, then temporarily offer an emergency stop.
Args:
scan_id(str|None): The scan id to stop. If None, the current scan will be stopped.
scan_id(str|None): The scan id to stop. If None, the current scan will be targeted.
"""
self.queue.request_scan_halt()
if self._emergency_stop_active:
self.queue.request_scan_halt()
self._activate_emergency_stop()
return
self.queue.request_scan_abortion()
self._activate_emergency_stop()
def _activate_emergency_stop(self) -> None:
self._emergency_stop_active = True
self._set_button_label(self.EMERGENCY_STOP_LABEL)
self._reset_timer.start(self.EMERGENCY_STOP_TIMEOUT_MS)
def _deactivate_emergency_stop(self) -> None:
self._emergency_stop_active = False
self._set_button_label(self.ABORT_LABEL)
def _set_button_label(self, label: str) -> None:
if hasattr(self.button, "setText"):
self.button.setText(label)
self.button.setToolTip(label)
def cleanup(self):
"""Stop and dispose the emergency-stop reset timer before widget teardown."""
self._reset_timer.stop()
self._reset_timer.timeout.disconnect(self._deactivate_emergency_stop)
self._reset_timer.deleteLater()
super().cleanup()
if __name__ == "__main__": # pragma: no cover
+42 -5
View File
@@ -1,5 +1,7 @@
# pylint: disable=missing-function-docstring, missing-module-docstring, unused-import
from unittest.mock import MagicMock
import pytest
from bec_widgets.widgets.control.buttons.stop_button.stop_button import StopButton
@@ -10,13 +12,48 @@ from .client_mocks import mocked_client
@pytest.fixture
def stop_button(qtbot, mocked_client):
widget = StopButton(client=mocked_client)
widget.queue = MagicMock()
qtbot.addWidget(widget)
qtbot.waitExposed(widget)
yield widget
yield widget, qtbot
def test_stop_button(stop_button):
assert stop_button.button.text() == "Stop"
stop_button.button.click()
assert stop_button.queue.request_scan_halt.called
stop_button.close()
widget, qtbot = stop_button
assert widget.button.text() == "Stop"
widget.button.click()
assert widget.queue.request_scan_abortion.called
assert widget.button.text() == "Emergency Stop"
qtbot.wait(widget.EMERGENCY_STOP_TIMEOUT_MS // 2)
widget.button.click()
assert widget.queue.request_scan_halt.called
qtbot.wait((widget.EMERGENCY_STOP_TIMEOUT_MS // 2) + 100)
assert widget.button.text() == "Emergency Stop"
qtbot.wait((widget.EMERGENCY_STOP_TIMEOUT_MS // 2) + 100)
assert widget.button.text() == "Stop"
widget.close()
def test_stop_button_click_extends_emergency_timeout(stop_button):
widget, qtbot = stop_button
widget.button.click()
qtbot.wait(widget.EMERGENCY_STOP_TIMEOUT_MS - 250)
widget.button.click()
assert widget.button.text() == "Emergency Stop"
qtbot.wait(200)
assert widget.button.text() == "Emergency Stop"
qtbot.wait(widget.EMERGENCY_STOP_TIMEOUT_MS - 100)
assert widget.button.text() == "Stop"
widget.close()