diff --git a/bec_widgets/widgets/services/device_browser/device_item/device_item.py b/bec_widgets/widgets/services/device_browser/device_item/device_item.py index 74b2ee82..8296db0f 100644 --- a/bec_widgets/widgets/services/device_browser/device_item/device_item.py +++ b/bec_widgets/widgets/services/device_browser/device_item/device_item.py @@ -6,10 +6,9 @@ from bec_lib.atlas_models import Device as DeviceConfigModel from bec_lib.devicemanager import DeviceContainer from bec_lib.logger import bec_logger from bec_qthemes import material_icon -from PySide6.QtWidgets import QTabWidget, QVBoxLayout from qtpy.QtCore import QMimeData, QSize, Qt, Signal from qtpy.QtGui import QDrag -from qtpy.QtWidgets import QApplication, QHBoxLayout, QToolButton, QWidget +from qtpy.QtWidgets import QApplication, QHBoxLayout, QTabWidget, QToolButton, QVBoxLayout, QWidget from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.expandable_frame import ExpandableGroupFrame diff --git a/bec_widgets/widgets/services/device_browser/device_item/device_signal_display.py b/bec_widgets/widgets/services/device_browser/device_item/device_signal_display.py index f1b9b2fe..9b5ff30b 100644 --- a/bec_widgets/widgets/services/device_browser/device_item/device_signal_display.py +++ b/bec_widgets/widgets/services/device_browser/device_item/device_signal_display.py @@ -1,9 +1,10 @@ +from bec_qthemes import material_icon from qtpy.QtCore import Qt -from qtpy.QtWidgets import QLabel, QVBoxLayout, QWidget +from qtpy.QtWidgets import QHBoxLayout, QLabel, QToolButton, QVBoxLayout, QWidget from bec_widgets.utils.bec_connector import ConnectionConfig from bec_widgets.utils.bec_widget import BECWidget -from bec_widgets.utils.error_popups import SafeProperty +from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.widgets.containers.dock.dock import BECDock from bec_widgets.widgets.utility.signal_label.signal_label import SignalLabel @@ -32,14 +33,35 @@ class SignalDisplay(BECWidget, QWidget): self._device = device self.device = device + @SafeSlot() + def _refresh(self): + if self.device in self.dev: + self.dev.get(self.device).read(cached=False) + self.dev.get(self.device).read_configuration(cached=False) + + def _add_refresh_button(self): + button_holder = QWidget() + button_holder.setLayout(QHBoxLayout()) + button_holder.layout().setAlignment(Qt.AlignmentFlag.AlignRight) + button_holder.layout().setContentsMargins(0, 0, 0, 0) + refresh_button = QToolButton() + refresh_button.setIcon( + material_icon(icon_name="refresh", size=(20, 20), convert_to_pixmap=False) + ) + refresh_button.clicked.connect(self._refresh) + button_holder.layout().addWidget(refresh_button) + self._content_layout.addWidget(button_holder) + def _populate(self): self._content.deleteLater() self._content = QWidget() self._layout.addWidget(self._content) self._content_layout = QVBoxLayout() - self._content_layout.setAlignment(Qt.AlignmentFlag.AlignTop) + self._content_layout.setContentsMargins(0, 0, 0, 0) self._content.setLayout(self._content_layout) + self._add_refresh_button() + if self._device in self.dev: for sig in self.dev[self.device]._info.get("signals", {}).keys(): self._content_layout.addWidget( diff --git a/bec_widgets/widgets/utility/signal_label/signal_label.py b/bec_widgets/widgets/utility/signal_label/signal_label.py index aebd9831..08b53755 100644 --- a/bec_widgets/widgets/utility/signal_label/signal_label.py +++ b/bec_widgets/widgets/utility/signal_label/signal_label.py @@ -179,6 +179,7 @@ class SignalLabel(BECWidget, QWidget): self._custom_units: str = custom_units self._show_default_units: bool = show_default_units self._decimal_places = 3 + self._dtype = None self._show_hinted_signals: bool = True self._show_normal_signals: bool = False @@ -240,8 +241,10 @@ class SignalLabel(BECWidget, QWidget): """Subscribe to the Redis topic for the device to display""" if not self._connected and self._device and self._device in self.dev: self._connected = True - self._readback_endpoint = MessageEndpoints.device_readback(self._device) - self.bec_dispatcher.connect_slot(self.on_device_readback, self._readback_endpoint) + self._read_endpoint = MessageEndpoints.device_read(self._device) + self._read_config_endpoint = MessageEndpoints.device_read_configuration(self._device) + self.bec_dispatcher.connect_slot(self.on_device_readback, self._read_endpoint) + self.bec_dispatcher.connect_slot(self.on_device_readback, self._read_config_endpoint) self._manual_read() self.set_display_value(self._value) @@ -249,7 +252,8 @@ class SignalLabel(BECWidget, QWidget): """Unsubscribe from the Redis topic for the device to display""" if self._connected: self._connected = False - self.bec_dispatcher.disconnect_slot(self.on_device_readback, self._readback_endpoint) + self.bec_dispatcher.disconnect_slot(self.on_device_readback, self._read_endpoint) + self.bec_dispatcher.disconnect_slot(self.on_device_readback, self._read_config_endpoint) def _manual_read(self): if self._device is None or not isinstance( @@ -258,8 +262,13 @@ class SignalLabel(BECWidget, QWidget): self._units = "" self._value = "__" return - signal: Signal = ( - getattr(device, self.signal, None) if isinstance(device, Device) else device + signal, info = ( + ( + getattr(device, self.signal, None), + device._info.get("signals", {}).get(self._signal, {}).get("describe", {}), + ) + if isinstance(device, Device) + else (device, device.describe().get(self._device)) ) if not isinstance(signal, Signal): # Avoid getting other attributes of device, e.g. methods signal = None @@ -268,7 +277,8 @@ class SignalLabel(BECWidget, QWidget): self._value = "__" return self._value = signal.get() - self._units = signal.get_device_config().get("egu", "") + self._units = info.get("egu", "") + self._dtype = info.get("dtype", "float") @SafeSlot(dict, dict) def on_device_readback(self, msg: dict, metadata: dict) -> None: @@ -277,8 +287,10 @@ class SignalLabel(BECWidget, QWidget): """ try: signal_to_read = self._patch_hinted_signal() - self._value = msg["signals"][signal_to_read]["value"] - self.set_display_value(self._value) + _value = msg["signals"].get(signal_to_read, {}).get("value") + if _value is not None: + self._value = _value + self.set_display_value(self._value) except Exception as e: self._display.setText("ERROR!") self._display.setToolTip( @@ -400,7 +412,10 @@ class SignalLabel(BECWidget, QWidget): if self._decimal_places == 0: return value try: - return f"{float(value):0.{self._decimal_places}f}" + if self._dtype in ("integer", "float"): + return f"{float(value):0.{self._decimal_places}f}" + else: + return str(value) except ValueError: return value diff --git a/tests/unit_tests/test_device_browser.py b/tests/unit_tests/test_device_browser.py index 62ad6e6c..0b31f6ea 100644 --- a/tests/unit_tests/test_device_browser.py +++ b/tests/unit_tests/test_device_browser.py @@ -3,6 +3,7 @@ from unittest import mock import pytest from qtpy.QtCore import QPoint, Qt +from qtpy.QtWidgets import QTabWidget from bec_widgets.widgets.services.device_browser.device_browser import DeviceBrowser from bec_widgets.widgets.services.device_browser.device_item.device_config_form import ( @@ -86,8 +87,13 @@ def test_device_item_expansion(device_browser, qtbot): device_item: QListWidgetItem = device_browser.ui.device_list.itemAt(0, 0) widget: DeviceItem = device_browser.ui.device_list.itemWidget(device_item) qtbot.mouseClick(widget._expansion_button, Qt.MouseButton.LeftButton) - form = widget._contents.layout().itemAt(0).widget() - qtbot.waitUntil(lambda: isinstance(form, DeviceConfigForm), timeout=500) + tab_widget: QTabWidget = widget._contents.layout().itemAt(0).widget() + qtbot.waitUntil(lambda: tab_widget.widget(0) is not None, timeout=100) + qtbot.waitUntil( + lambda: isinstance(tab_widget.widget(0).layout().itemAt(0).widget(), DeviceConfigForm), + timeout=100, + ) + form = tab_widget.widget(0).layout().itemAt(0).widget() assert widget.expanded assert (name_field := form.widget_dict.get("name")) is not None qtbot.waitUntil(lambda: name_field.getValue() == "samx", timeout=500) diff --git a/tests/unit_tests/test_signal_label.py b/tests/unit_tests/test_signal_label.py index 08ba13f2..6bf543d2 100644 --- a/tests/unit_tests/test_signal_label.py +++ b/tests/unit_tests/test_signal_label.py @@ -121,11 +121,13 @@ def test_custom_label(signal_label: SignalLabel, qtbot): def test_units_in_display(signal_label: SignalLabel, qtbot): signal_label._value = "1.8" + signal_label._dtype = "float" signal_label.custom_units = "Mfurlong μfortnight⁻¹" assert signal_label._display.text() == "1.800 Mfurlong μfortnight⁻¹" def test_decimal_places(signal_label: SignalLabel, qtbot): + signal_label._dtype = "float" signal_label.decimal_places = 2 signal_label.set_display_value("123.456") assert signal_label._display.text() == "123.46 m/s" @@ -226,6 +228,7 @@ def test_handle_readback(signal_label: SignalLabel, qtbot): signal_label.device = "samx" signal_label.signal = "readback" signal_label.custom_units = "μm" + signal_label._dtype = "float" signal_label.on_device_readback({"random": {"stuff": "in", "corrupted": "reading"}}, {}) assert signal_label._display.text() == "ERROR!" assert "Error processing incoming reading" in signal_label._display.toolTip()