diff --git a/bec_widgets/widgets/services/device_browser/device_browser.py b/bec_widgets/widgets/services/device_browser/device_browser.py
index 1a1f647c..d03ee93d 100644
--- a/bec_widgets/widgets/services/device_browser/device_browser.py
+++ b/bec_widgets/widgets/services/device_browser/device_browser.py
@@ -1,7 +1,9 @@
import os
import re
from functools import partial
+from typing import Callable
+import bec_lib
from bec_lib.callback_handler import EventType
from bec_lib.config_helper import ConfigHelper
from bec_lib.endpoints import MessageEndpoints
@@ -10,7 +12,14 @@ from bec_lib.messages import ConfigAction, ScanStatusMessage
from bec_qthemes import material_icon
from pyqtgraph import SignalProxy
from qtpy.QtCore import QSize, QThreadPool, Signal
-from qtpy.QtWidgets import QLabel, QListWidget, QListWidgetItem, QToolButton, QVBoxLayout, QWidget
+from qtpy.QtWidgets import (
+ QFileDialog,
+ QListWidget,
+ QListWidgetItem,
+ QToolButton,
+ QVBoxLayout,
+ QWidget,
+)
from bec_widgets.cli.rpc.rpc_register import RPCRegister
from bec_widgets.utils.bec_widget import BECWidget
@@ -61,12 +70,14 @@ class DeviceBrowser(BECWidget, QWidget):
self.bec_dispatcher.client.callbacks.register(
EventType.SCAN_STATUS, self.scan_status_changed
)
+ self._default_config_dir = os.path.abspath(
+ os.path.join(os.path.dirname(bec_lib.__file__), "./configs/")
+ )
self.devices_changed.connect(self.update_device_list)
- self.ui.add_button.clicked.connect(self._create_add_dialog)
- self.ui.add_button.setIcon(material_icon("add", size=(20, 20), convert_to_pixmap=False))
self.init_warning_label()
+ self.init_tool_buttons()
self.init_device_list()
self.update_device_list()
@@ -90,6 +101,18 @@ class DeviceBrowser(BECWidget, QWidget):
initial_status = scan_status.status if scan_status is not None else "closed"
self.set_editing_mode(initial_status not in ["open", "paused"])
+ def init_tool_buttons(self):
+ def _setup_button(button: QToolButton, icon: str, slot: Callable, tooltip: str = ""):
+ button.clicked.connect(slot)
+ button.setIcon(material_icon(icon, size=(20, 20), convert_to_pixmap=False))
+ button.setToolTip(tooltip)
+
+ _setup_button(self.ui.add_button, "add", self._create_add_dialog, "add new device")
+ _setup_button(self.ui.save_button, "save", self._save_to_file, "save config to file")
+ _setup_button(
+ self.ui.import_button, "input", self._load_from_file, "append/merge config from file"
+ )
+
def _create_add_dialog(self):
dialog = DeviceConfigDialog(parent=self, device=None, action="add")
dialog.open()
@@ -148,7 +171,7 @@ class DeviceBrowser(BECWidget, QWidget):
self.dev_list.addItem(item)
self._device_items[device] = item
- @SafeSlot(bool)
+ @SafeSlot(dict, dict)
def scan_status_changed(self, scan_info: dict, _: dict):
"""disable editing when scans are running and enable editing when they are finished"""
msg = ScanStatusMessage.model_validate(scan_info)
@@ -190,6 +213,22 @@ class DeviceBrowser(BECWidget, QWidget):
for device in self.dev:
self._device_items[device].setHidden(not self.regex.search(device))
+ @SafeSlot()
+ def _load_from_file(self):
+ file_path, _ = QFileDialog.getOpenFileName(
+ self, "Update config from file", self._default_config_dir, "Config files (*.yml *.yaml)"
+ )
+ if file_path:
+ self._config_helper.update_session_with_file(file_path)
+
+ @SafeSlot()
+ def _save_to_file(self):
+ file_path, _ = QFileDialog.getSaveFileName(
+ self, "Save config to file", self._default_config_dir, "Config files (*.yml *.yaml)"
+ )
+ if file_path:
+ self._config_helper.save_current_session(file_path)
+
if __name__ == "__main__": # pragma: no cover
import sys
diff --git a/bec_widgets/widgets/services/device_browser/device_browser.ui b/bec_widgets/widgets/services/device_browser/device_browser.ui
index 8f53f709..9a2d4ce2 100644
--- a/bec_widgets/widgets/services/device_browser/device_browser.ui
+++ b/bec_widgets/widgets/services/device_browser/device_browser.ui
@@ -51,6 +51,20 @@
+ -
+
+
+ ...
+
+
+
+ -
+
+
+ ...
+
+
+
diff --git a/bec_widgets/widgets/services/device_browser/device_item/config_communicator.py b/bec_widgets/widgets/services/device_browser/device_item/config_communicator.py
index 2326734a..4a469dbb 100644
--- a/bec_widgets/widgets/services/device_browser/device_item/config_communicator.py
+++ b/bec_widgets/widgets/services/device_browser/device_item/config_communicator.py
@@ -39,9 +39,10 @@ class CommunicateConfigAction(QRunnable):
raise ValueError(
"Must be updating a device or be supplied a name for a new device"
)
- if "deviceConfig" not in self.config:
+ if "deviceConfig" not in self.config or self.action in ["add", "remove"]:
self.process_simple_action(dev_name)
else:
+ # updating an existing device, but need to recreate it for this change
self.process_remove_readd(dev_name)
else:
raise ValueError(f"action {self.action} is not supported")
diff --git a/bec_widgets/widgets/services/device_browser/device_item/device_config_dialog.py b/bec_widgets/widgets/services/device_browser/device_item/device_config_dialog.py
index c9b594c7..f952f2c1 100644
--- a/bec_widgets/widgets/services/device_browser/device_item/device_config_dialog.py
+++ b/bec_widgets/widgets/services/device_browser/device_item/device_config_dialog.py
@@ -68,7 +68,7 @@ class DeviceConfigDialog(BECWidget, QDialog):
self.client.connector, self.client._service_name
)
self._device = device
- self._action = action
+ self._action: Literal["update", "add"] = action
self._q_threadpool = threadpool or QThreadPool()
self.setWindowTitle(f"Edit config for: {device}")
self._container = QStackedLayout()
@@ -168,10 +168,9 @@ class DeviceConfigDialog(BECWidget, QDialog):
diff = {
k: v for k, v in new_config.items() if self._initial_config.get(k) != new_config.get(k)
}
- if self._initial_config["deviceConfig"] in [{}, None] and new_config["deviceConfig"] in [
- {},
- None,
- ]:
+ if self._initial_config.get("deviceConfig") in [{}, None] and new_config.get(
+ "deviceConfig"
+ ) in [{}, None]:
diff.pop("deviceConfig", None)
if diff.get("deviceConfig") is not None:
# TODO: special cased in some parts of device manager but not others, should
@@ -222,7 +221,7 @@ class DeviceConfigDialog(BECWidget, QDialog):
self._proc_device_config_change(updated_config)
def _proc_device_config_change(self, config: dict):
- logger.info(f"Sending request to update device config: {config}")
+ logger.info(f"Sending request to {self._action} device config: {config}")
self._start_waiting_display()
communicate_update = CommunicateConfigAction(