bec-widgets 0.49.1__tar.gz → 0.50.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/PKG-INFO +1 -1
  2. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/cli/client.py +69 -4
  3. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/cli/client_utils.py +21 -13
  4. bec_widgets-0.50.1/bec_widgets/cli/rpc_register.py +76 -0
  5. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/cli/server.py +16 -21
  6. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/jupyter_console/jupyter_console_window.py +5 -0
  7. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/bec_connector.py +21 -1
  8. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/bec_dispatcher.py +11 -4
  9. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/figure/figure.py +23 -22
  10. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/plots/image.py +14 -24
  11. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/plots/motor_map.py +12 -19
  12. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/plots/plot_base.py +1 -0
  13. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/plots/waveform.py +18 -14
  14. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets.egg-info/PKG-INFO +1 -1
  15. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets.egg-info/SOURCES.txt +6 -0
  16. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/setup.py +1 -1
  17. bec_widgets-0.50.1/tests/end-2-end/conftest.py +25 -0
  18. bec_widgets-0.50.1/tests/end-2-end/test_bec_figure_rpc_e2e.py +165 -0
  19. bec_widgets-0.50.1/tests/end-2-end/test_rpc_register_e2e.py +50 -0
  20. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/conftest.py +7 -0
  21. bec_widgets-0.50.1/tests/unit_tests/test_msgs/__init__.py +0 -0
  22. bec_widgets-0.50.1/tests/unit_tests/test_rpc_register.py +52 -0
  23. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/LICENSE +0 -0
  24. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/README.md +0 -0
  25. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/__init__.py +0 -0
  26. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/cli/__init__.py +0 -0
  27. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/cli/auto_updates.py +0 -0
  28. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/cli/bec_widgets_icon.png +0 -0
  29. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/cli/generate_cli.py +0 -0
  30. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/__init__.py +0 -0
  31. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/eiger_plot/__init__.py +0 -0
  32. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/eiger_plot/eiger_plot.py +0 -0
  33. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/eiger_plot/eiger_plot.ui +0 -0
  34. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/jupyter_console/__init__.py +0 -0
  35. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/jupyter_console/jupyter_console_window.ui +0 -0
  36. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/mca_readout/__init__.py +0 -0
  37. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/mca_readout/mca_plot.py +0 -0
  38. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/mca_readout/mca_sim.py +0 -0
  39. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/motor_movement/__init__.py +0 -0
  40. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/motor_movement/config_example.yaml +0 -0
  41. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/motor_movement/csax_bec_config.yaml +0 -0
  42. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/motor_movement/csaxs_config.yaml +0 -0
  43. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/motor_movement/motor_control_compilations.py +0 -0
  44. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/motor_movement/motor_controller.ui +0 -0
  45. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/motor_movement/motor_example.py +0 -0
  46. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/stream_plot/__init__.py +0 -0
  47. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/stream_plot/line_plot.ui +0 -0
  48. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/examples/stream_plot/stream_plot.py +0 -0
  49. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/simulations/__init__.py +0 -0
  50. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/__init__.py +0 -0
  51. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/bec_table.py +0 -0
  52. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/colors.py +0 -0
  53. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/container_utils.py +0 -0
  54. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/crosshair.py +0 -0
  55. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/ctrl_c.py +0 -0
  56. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/entry_validator.py +0 -0
  57. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/rpc_decorator.py +0 -0
  58. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/thread_checker.py +0 -0
  59. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/validator_delegate.py +0 -0
  60. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/widget_io.py +0 -0
  61. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/utils/yaml_dialog.py +0 -0
  62. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/validation/__init__.py +0 -0
  63. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/validation/monitor_config_validator.py +0 -0
  64. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/__init__.py +0 -0
  65. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/figure/__init__.py +0 -0
  66. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/monitor/__init__.py +0 -0
  67. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/monitor/config_dialog.py +0 -0
  68. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/monitor/config_dialog.ui +0 -0
  69. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/monitor/monitor.py +0 -0
  70. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/monitor/tab_template.ui +0 -0
  71. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/motor_control/__init__.py +0 -0
  72. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/motor_control/motor_control.py +0 -0
  73. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/motor_control/motor_control_absolute.ui +0 -0
  74. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/motor_control/motor_control_relative.ui +0 -0
  75. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/motor_control/motor_control_selection.ui +0 -0
  76. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/motor_control/motor_control_table.ui +0 -0
  77. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/motor_map/__init__.py +0 -0
  78. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/motor_map/motor_map.py +0 -0
  79. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/plots/__init__.py +0 -0
  80. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/scan_control/__init__.py +0 -0
  81. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/scan_control/scan_control.py +0 -0
  82. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/toolbar/__init__.py +0 -0
  83. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets/widgets/toolbar/toolbar.py +0 -0
  84. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets.egg-info/dependency_links.txt +0 -0
  85. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets.egg-info/requires.txt +0 -0
  86. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/bec_widgets.egg-info/top_level.txt +0 -0
  87. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/setup.cfg +0 -0
  88. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/__init__.py +0 -0
  89. {bec_widgets-0.49.1/tests/unit_tests → bec_widgets-0.50.1/tests/end-2-end}/__init__.py +0 -0
  90. {bec_widgets-0.49.1/tests/unit_tests/test_msgs → bec_widgets-0.50.1/tests/unit_tests}/__init__.py +0 -0
  91. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/client_mocks.py +0 -0
  92. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_bec_connector.py +0 -0
  93. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_bec_dispatcher.py +0 -0
  94. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_bec_figure.py +0 -0
  95. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_bec_monitor.py +0 -0
  96. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_bec_motor_map.py +0 -0
  97. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_client_utils.py +0 -0
  98. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_config_dialog.py +0 -0
  99. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_crosshair.py +0 -0
  100. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_eiger_plot.py +0 -0
  101. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_generate_cli_client.py +0 -0
  102. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_motor_control.py +0 -0
  103. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_motor_map.py +0 -0
  104. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_msgs/available_scans_message.py +0 -0
  105. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_plot_base.py +0 -0
  106. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_scan_control.py +0 -0
  107. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_stream_plot.py +0 -0
  108. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_validator_errors.py +0 -0
  109. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_waveform1d.py +0 -0
  110. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_widget_io.py +0 -0
  111. {bec_widgets-0.49.1 → bec_widgets-0.50.1}/tests/unit_tests/test_yaml_dialog.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bec_widgets
3
- Version: 0.49.1
3
+ Version: 0.50.1
4
4
  Summary: BEC Widgets
5
5
  Home-page: https://gitlab.psi.ch/bec/bec-widgets
6
6
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec-widgets/issues
@@ -136,6 +136,13 @@ class BECPlotBase(RPCBase):
136
136
 
137
137
 
138
138
  class BECWaveform(RPCBase):
139
+ @property
140
+ @rpc_call
141
+ def rpc_id(self) -> "str":
142
+ """
143
+ Get the RPC ID of the widget.
144
+ """
145
+
139
146
  @property
140
147
  @rpc_call
141
148
  def config_dict(self) -> "dict":
@@ -387,6 +394,13 @@ class BECWaveform(RPCBase):
387
394
 
388
395
 
389
396
  class BECFigure(RPCBase, BECFigureClientMixin):
397
+ @property
398
+ @rpc_call
399
+ def rpc_id(self) -> "str":
400
+ """
401
+ Get the RPC ID of the widget.
402
+ """
403
+
390
404
  @property
391
405
  @rpc_call
392
406
  def config_dict(self) -> "dict":
@@ -396,13 +410,16 @@ class BECFigure(RPCBase, BECFigureClientMixin):
396
410
  dict: The configuration of the widget.
397
411
  """
398
412
 
399
- @property
400
413
  @rpc_call
401
- def axes(self) -> "list[BECPlotBase]":
414
+ def axes(self, row: "int", col: "int") -> "BECPlotBase":
402
415
  """
403
- Access all widget in BECFigure as a list
416
+ Get widget by its coordinates in the figure.
417
+ Args:
418
+ row(int): the row coordinate
419
+ col(int): the column coordinate
420
+
404
421
  Returns:
405
- list[BECPlotBase]: List of all widgets in the figure.
422
+ BECPlotBase: the widget at the given coordinates
406
423
  """
407
424
 
408
425
  @property
@@ -614,8 +631,36 @@ class BECFigure(RPCBase, BECFigureClientMixin):
614
631
  Clear all widgets from the figure and reset to default state
615
632
  """
616
633
 
634
+ @rpc_call
635
+ def get_all_rpc(self) -> "dict":
636
+ """
637
+ Get all registered RPC objects.
638
+ """
639
+
640
+ @property
641
+ @rpc_call
642
+ def widget_list(self) -> "list[BECPlotBase]":
643
+ """
644
+ Access all widget in BECFigure as a list
645
+ Returns:
646
+ list[BECPlotBase]: List of all widgets in the figure.
647
+ """
648
+
617
649
 
618
650
  class BECCurve(RPCBase):
651
+ @rpc_call
652
+ def remove(self):
653
+ """
654
+ Remove the curve from the plot.
655
+ """
656
+
657
+ @property
658
+ @rpc_call
659
+ def rpc_id(self) -> "str":
660
+ """
661
+ Get the RPC ID of the widget.
662
+ """
663
+
619
664
  @property
620
665
  @rpc_call
621
666
  def config_dict(self) -> "dict":
@@ -713,6 +758,13 @@ class BECCurve(RPCBase):
713
758
 
714
759
 
715
760
  class BECImageShow(RPCBase):
761
+ @property
762
+ @rpc_call
763
+ def rpc_id(self) -> "str":
764
+ """
765
+ Get the RPC ID of the widget.
766
+ """
767
+
716
768
  @property
717
769
  @rpc_call
718
770
  def config_dict(self) -> "dict":
@@ -1045,8 +1097,21 @@ class BECConnector(RPCBase):
1045
1097
  dict: The configuration of the widget.
1046
1098
  """
1047
1099
 
1100
+ @rpc_call
1101
+ def get_all_rpc(self) -> "dict":
1102
+ """
1103
+ Get all registered RPC objects.
1104
+ """
1105
+
1048
1106
 
1049
1107
  class BECImageItem(RPCBase):
1108
+ @property
1109
+ @rpc_call
1110
+ def rpc_id(self) -> "str":
1111
+ """
1112
+ Get the RPC ID of the widget.
1113
+ """
1114
+
1050
1115
  @property
1051
1116
  @rpc_call
1052
1117
  def config_dict(self) -> "dict":
@@ -12,7 +12,7 @@ import uuid
12
12
  from functools import wraps
13
13
  from typing import TYPE_CHECKING
14
14
 
15
- from bec_lib import MessageEndpoints, messages
15
+ from bec_lib import MessageEndpoints, ServiceConfig, messages
16
16
  from bec_lib.connector import MessageObject
17
17
  from bec_lib.device import DeviceBase
18
18
  from qtpy.QtCore import QCoreApplication
@@ -136,10 +136,11 @@ class BECFigureClientMixin:
136
136
  """
137
137
  self._start_update_script()
138
138
  # pylint: disable=subprocess-run-check
139
+ config = self._client._service_config.redis
139
140
  monitor_module = importlib.import_module("bec_widgets.cli.server")
140
141
  monitor_path = monitor_module.__file__
141
142
 
142
- command = [sys.executable, "-u", monitor_path, "--id", self._gui_id]
143
+ command = [sys.executable, "-u", monitor_path, "--id", self._gui_id, "--config", config]
143
144
  self._process = subprocess.Popen(
144
145
  command, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
145
146
  )
@@ -157,17 +158,24 @@ class BECFigureClientMixin:
157
158
  self.stderr_output.clear()
158
159
 
159
160
  def _get_output(self) -> str:
160
- os.set_blocking(self._process.stdout.fileno(), False)
161
- os.set_blocking(self._process.stderr.fileno(), False)
162
- while self._process.poll() is None:
163
- readylist, _, _ = select.select([self._process.stdout, self._process.stderr], [], [], 1)
164
- if self._process.stdout in readylist:
165
- # print("*"*10, self._process.stdout.read(1024), flush=True, end="")
166
- self._process.stdout.read(1024)
167
- if self._process.stderr in readylist:
168
- # print("!"*10, self._process.stderr.read(1024), flush=True, end="", file=sys.stderr)
169
- print(self._process.stderr.read(1024), flush=True, end="", file=sys.stderr)
170
- self.stderr_output.append(self._process.stderr.read(1024))
161
+ try:
162
+ os.set_blocking(self._process.stdout.fileno(), False)
163
+ os.set_blocking(self._process.stderr.fileno(), False)
164
+ while self._process.poll() is None:
165
+ readylist, _, _ = select.select(
166
+ [self._process.stdout, self._process.stderr], [], [], 1
167
+ )
168
+ if self._process.stdout in readylist:
169
+ output = self._process.stdout.read(1024)
170
+ if output:
171
+ print(output, end="")
172
+ if self._process.stderr in readylist:
173
+ error_output = self._process.stderr.read(1024)
174
+ if error_output:
175
+ print(error_output, end="", file=sys.stderr)
176
+ self.stderr_output.append(error_output)
177
+ except Exception as e:
178
+ print(f"Error reading process output: {str(e)}")
171
179
 
172
180
 
173
181
  class RPCResponseTimeoutError(Exception):
@@ -0,0 +1,76 @@
1
+ from threading import Lock
2
+ from weakref import WeakValueDictionary
3
+
4
+ from qtpy.QtCore import QObject
5
+
6
+
7
+ class RPCRegister:
8
+ """
9
+ A singleton class that keeps track of all the RPC objects registered in the system for CLI usage.
10
+ """
11
+
12
+ _instance = None
13
+ _initialized = False
14
+ _lock = Lock()
15
+
16
+ def __new__(cls, *args, **kwargs):
17
+ if cls._instance is None:
18
+ cls._instance = super(RPCRegister, cls).__new__(cls)
19
+ cls._initialized = False
20
+ return cls._instance
21
+
22
+ def __init__(self):
23
+ if self._initialized:
24
+ return
25
+ self._rpc_register = WeakValueDictionary()
26
+ self._initialized = True
27
+
28
+ def add_rpc(self, rpc: QObject):
29
+ """
30
+ Add an RPC object to the register.
31
+ Args:
32
+ rpc(QObject): The RPC object to be added to the register.
33
+ """
34
+ if not hasattr(rpc, "gui_id"):
35
+ raise ValueError("RPC object must have a 'gui_id' attribute.")
36
+ self._rpc_register[rpc.gui_id] = rpc
37
+
38
+ def remove_rpc(self, rpc: str):
39
+ """
40
+ Remove an RPC object from the register.
41
+ Args:
42
+ rpc(str): The RPC object to be removed from the register.
43
+ """
44
+ if not hasattr(rpc, "gui_id"):
45
+ raise ValueError(f"RPC object {rpc} must have a 'gui_id' attribute.")
46
+ self._rpc_register.pop(rpc.gui_id, None)
47
+
48
+ def get_rpc_by_id(self, gui_id: str) -> QObject:
49
+ """
50
+ Get an RPC object by its ID.
51
+ Args:
52
+ gui_id(str): The ID of the RPC object to be retrieved.
53
+
54
+ Returns:
55
+ QObject: The RPC object with the given ID.
56
+ """
57
+ rpc_object = self._rpc_register.get(gui_id, None)
58
+ return rpc_object
59
+
60
+ def list_all_connections(self) -> dict:
61
+ """
62
+ List all the registered RPC objects.
63
+ Returns:
64
+ dict: A dictionary containing all the registered RPC objects.
65
+ """
66
+ with self._lock:
67
+ connections = dict(self._rpc_register)
68
+ return connections
69
+
70
+ @classmethod
71
+ def reset_singleton(cls):
72
+ """
73
+ Reset the singleton instance.
74
+ """
75
+ cls._instance = None
76
+ cls._initialized = False
@@ -5,6 +5,7 @@ import time
5
5
  from bec_lib import MessageEndpoints, messages
6
6
  from qtpy.QtCore import QTimer
7
7
 
8
+ from bec_widgets.cli.rpc_register import RPCRegister
8
9
  from bec_widgets.utils import BECDispatcher
9
10
  from bec_widgets.utils.bec_connector import BECConnector
10
11
  from bec_widgets.widgets.figure import BECFigure
@@ -14,12 +15,16 @@ from bec_widgets.widgets.plots import BECCurve, BECImageShow, BECWaveform
14
15
  class BECWidgetsCLIServer:
15
16
  WIDGETS = [BECWaveform, BECFigure, BECCurve, BECImageShow]
16
17
 
17
- def __init__(self, gui_id: str = None, dispatcher: BECDispatcher = None, client=None) -> None:
18
- self.dispatcher = BECDispatcher() if dispatcher is None else dispatcher
18
+ def __init__(
19
+ self, gui_id: str = None, dispatcher: BECDispatcher = None, client=None, config=None
20
+ ) -> None:
21
+ self.dispatcher = BECDispatcher(config=config) if dispatcher is None else dispatcher
19
22
  self.client = self.dispatcher.client if client is None else client
20
23
  self.client.start()
21
24
  self.gui_id = gui_id
22
25
  self.fig = BECFigure(gui_id=self.gui_id)
26
+ self.rpc_register = RPCRegister()
27
+ self.rpc_register.add_rpc(self.fig)
23
28
 
24
29
  self.dispatcher.connect_slot(
25
30
  self.on_rpc_update, MessageEndpoints.gui_instructions(self.gui_id)
@@ -29,15 +34,15 @@ class BECWidgetsCLIServer:
29
34
  self._shutdown_event = False
30
35
  self._heartbeat_timer = QTimer()
31
36
  self._heartbeat_timer.timeout.connect(self.emit_heartbeat)
32
- self._heartbeat_timer.start(1000) # Emit heartbeat every 1 seconds
37
+ self._heartbeat_timer.start(200) # Emit heartbeat every 1 seconds
33
38
 
34
39
  def on_rpc_update(self, msg: dict, metadata: dict):
35
40
  request_id = metadata.get("request_id")
36
41
  try:
42
+ obj = self.get_object_from_config(msg["parameter"])
37
43
  method = msg["action"]
38
44
  args = msg["parameter"].get("args", [])
39
45
  kwargs = msg["parameter"].get("kwargs", {})
40
- obj = self.get_object_from_config(msg["parameter"])
41
46
  res = self.run_rpc(obj, method, args, kwargs)
42
47
  except Exception as e:
43
48
  print(e)
@@ -54,20 +59,10 @@ class BECWidgetsCLIServer:
54
59
 
55
60
  def get_object_from_config(self, config: dict):
56
61
  gui_id = config.get("gui_id")
57
- # check if the object is the figure
58
- if gui_id == self.fig.gui_id:
59
- return self.fig
60
- # check if the object is a widget
61
- if gui_id in self.fig._widgets:
62
- obj = self.fig._widgets[config["gui_id"]]
63
- return obj
64
- if self.fig._widgets:
65
- for widget in self.fig._widgets.values():
66
- item = widget.find_widget_by_id(gui_id)
67
- if item:
68
- return item
69
-
70
- raise ValueError(f"Object with gui_id {gui_id} not found")
62
+ obj = self.rpc_register.get_rpc_by_id(gui_id)
63
+ if obj is None:
64
+ raise ValueError(f"Object with gui_id {gui_id} not found")
65
+ return obj
71
66
 
72
67
  def run_rpc(self, obj, method, args, kwargs):
73
68
  method_obj = getattr(obj, method)
@@ -106,7 +101,6 @@ class BECWidgetsCLIServer:
106
101
  messages.StatusMessage(name=self.gui_id, status=1, info={}),
107
102
  expire=10,
108
103
  )
109
- print("Heartbeat emitted")
110
104
 
111
105
  def shutdown(self):
112
106
  self._shutdown_event = True
@@ -133,11 +127,12 @@ if __name__ == "__main__": # pragma: no cover
133
127
 
134
128
  parser = argparse.ArgumentParser(description="BEC Widgets CLI Server")
135
129
  parser.add_argument("--id", type=str, help="The id of the server")
130
+ parser.add_argument("--config", type=str, help="Config to connect to redis.")
136
131
 
137
132
  args = parser.parse_args()
138
133
 
139
- server = BECWidgetsCLIServer(gui_id=args.id)
140
- # server = BECWidgetsCLIServer(gui_id="test")
134
+ server = BECWidgetsCLIServer(gui_id=args.id, config=args.config)
135
+ # server = BECWidgetsCLIServer(gui_id="test",config="awi-bec-dev-01:6379")
141
136
 
142
137
  fig = server.fig
143
138
  win.setCentralWidget(fig)
@@ -7,6 +7,7 @@ from qtconsole.inprocess import QtInProcessKernelManager
7
7
  from qtconsole.rich_jupyter_widget import RichJupyterWidget
8
8
  from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
9
9
 
10
+ from bec_widgets.cli.rpc_register import RPCRegister
10
11
  from bec_widgets.utils import BECDispatcher
11
12
  from bec_widgets.widgets import BECFigure
12
13
 
@@ -43,10 +44,14 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
43
44
  self.safe_close = False
44
45
  # self.figure.clean_signal.connect(self.confirm_close)
45
46
 
47
+ self.register = RPCRegister()
48
+ self.register.add_rpc(self.figure)
49
+ print("Registered objects:", dict(self.register.list_all_connections()))
46
50
  # console push
47
51
  self.console.kernel_manager.kernel.shell.push(
48
52
  {
49
53
  "fig": self.figure,
54
+ "register": self.register,
50
55
  "w1": self.w1,
51
56
  "w2": self.w2,
52
57
  "w3": self.w3,
@@ -7,6 +7,7 @@ from typing import Optional, Type
7
7
  from pydantic import BaseModel, Field, field_validator
8
8
  from qtpy.QtCore import Slot as pyqtSlot
9
9
 
10
+ from bec_widgets.cli.rpc_register import RPCRegister
10
11
  from bec_widgets.utils.bec_dispatcher import BECDispatcher
11
12
 
12
13
 
@@ -31,7 +32,7 @@ class ConnectionConfig(BaseModel):
31
32
  class BECConnector:
32
33
  """Connection mixin class for all BEC widgets, to handle BEC client and device manager"""
33
34
 
34
- USER_ACCESS = ["config_dict"]
35
+ USER_ACCESS = ["config_dict", "get_all_rpc"]
35
36
 
36
37
  def __init__(self, client=None, config: ConnectionConfig = None, gui_id: str = None):
37
38
  # BEC related connections
@@ -54,6 +55,25 @@ class BECConnector:
54
55
  else:
55
56
  self.gui_id = self.config.gui_id
56
57
 
58
+ # register widget to rpc register
59
+ self.rpc_register = RPCRegister()
60
+ self.rpc_register.add_rpc(self)
61
+
62
+ def get_all_rpc(self) -> dict:
63
+ """Get all registered RPC objects."""
64
+ all_connections = self.rpc_register.list_all_connections()
65
+ return dict(all_connections)
66
+
67
+ @property
68
+ def rpc_id(self) -> str:
69
+ """Get the RPC ID of the widget."""
70
+ return self.gui_id
71
+
72
+ @rpc_id.setter
73
+ def rpc_id(self, rpc_id: str) -> None:
74
+ """Set the RPC ID of the widget."""
75
+ self.gui_id = rpc_id
76
+
57
77
  @property
58
78
  def config_dict(self) -> dict:
59
79
  """
@@ -6,7 +6,7 @@ from collections.abc import Callable
6
6
  from typing import TYPE_CHECKING, Union
7
7
 
8
8
  import redis
9
- from bec_lib import BECClient
9
+ from bec_lib import BECClient, ServiceConfig
10
10
  from bec_lib.redis_connector import MessageObject, RedisConnector
11
11
  from qtpy.QtCore import QObject
12
12
  from qtpy.QtCore import Signal as pyqtSignal
@@ -71,13 +71,13 @@ class BECDispatcher:
71
71
  _instance = None
72
72
  _initialized = False
73
73
 
74
- def __new__(cls, client=None, *args, **kwargs):
74
+ def __new__(cls, client=None, config: str = None, *args, **kwargs):
75
75
  if cls._instance is None:
76
76
  cls._instance = super(BECDispatcher, cls).__new__(cls)
77
77
  cls._initialized = False
78
78
  return cls._instance
79
79
 
80
- def __init__(self, client=None):
80
+ def __init__(self, client=None, config: str = None):
81
81
  if self._initialized:
82
82
  return
83
83
 
@@ -85,7 +85,14 @@ class BECDispatcher:
85
85
  self.client = client
86
86
 
87
87
  if self.client is None:
88
- self.client = BECClient(connector_cls=QtRedisConnector, forced=True)
88
+ if config is not None:
89
+ host, port = config.split(":")
90
+ redis_config = {"host": host, "port": port}
91
+ self.client = BECClient(
92
+ config=ServiceConfig(redis=redis_config), connector_cls=QtRedisConnector
93
+ ) # , forced=True)
94
+ else:
95
+ self.client = BECClient(connector_cls=QtRedisConnector) # , forced=True)
89
96
  else:
90
97
  if self.client.started:
91
98
  # have to reinitialize client to use proper connector
@@ -97,6 +97,7 @@ class WidgetHandler:
97
97
 
98
98
  class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
99
99
  USER_ACCESS = [
100
+ "rpc_id",
100
101
  "config_dict",
101
102
  "axes",
102
103
  "widgets",
@@ -110,6 +111,8 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
110
111
  "change_layout",
111
112
  "change_theme",
112
113
  "clear_all",
114
+ "get_all_rpc",
115
+ "widget_list",
113
116
  ]
114
117
 
115
118
  clean_signal = pyqtSignal()
@@ -138,8 +141,21 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
138
141
  # Container to keep track of the grid
139
142
  self.grid = []
140
143
 
144
+ def __getitem__(self, key: tuple | str):
145
+ if isinstance(key, tuple) and len(key) == 2:
146
+ return self.axes(*key)
147
+ elif isinstance(key, str):
148
+ widget = self._widgets.get(key)
149
+ if widget is None:
150
+ raise KeyError(f"No widget with ID {key}")
151
+ return self._widgets.get(key)
152
+ else:
153
+ raise TypeError(
154
+ "Key must be a string (widget id) or a tuple of two integers (grid coordinates)"
155
+ )
156
+
141
157
  @property
142
- def axes(self) -> list[BECPlotBase]:
158
+ def widget_list(self) -> list[BECPlotBase]:
143
159
  """
144
160
  Access all widget in BECFigure as a list
145
161
  Returns:
@@ -148,8 +164,8 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
148
164
  axes = [value for value in self._widgets.values() if isinstance(value, BECPlotBase)]
149
165
  return axes
150
166
 
151
- @axes.setter
152
- def axes(self, value: list[BECPlotBase]):
167
+ @widget_list.setter
168
+ def widget_list(self, value: list[BECPlotBase]):
153
169
  self._axes = value
154
170
 
155
171
  @property
@@ -623,7 +639,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
623
639
  row(int): The row coordinate of the widget to remove.
624
640
  col(int): The column coordinate of the widget to remove.
625
641
  """
626
- widget = self._get_widget_by_coordinates(row, col)
642
+ widget = self.axes(row, col)
627
643
  if widget:
628
644
  widget_id = widget.config.gui_id
629
645
  if widget_id in self._widgets:
@@ -643,24 +659,10 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
643
659
  self._reindex_grid()
644
660
  if widget_id in self.config.widgets:
645
661
  self.config.widgets.pop(widget_id)
646
- print(f"Removed widget {widget_id}.")
647
662
  else:
648
663
  raise ValueError(f"Widget with ID '{widget_id}' does not exist.")
649
664
 
650
- def __getitem__(self, key: tuple | str):
651
- if isinstance(key, tuple) and len(key) == 2:
652
- return self._get_widget_by_coordinates(*key)
653
- elif isinstance(key, str):
654
- widget = self._widgets.get(key)
655
- if widget is None:
656
- raise KeyError(f"No widget with ID {key}")
657
- return self._widgets.get(key)
658
- else:
659
- raise TypeError(
660
- "Key must be a string (widget id) or a tuple of two integers (grid coordinates)"
661
- )
662
-
663
- def _get_widget_by_coordinates(self, row: int, col: int) -> BECPlotBase:
665
+ def axes(self, row: int, col: int) -> BECPlotBase:
664
666
  """
665
667
  Get widget by its coordinates in the figure.
666
668
  Args:
@@ -699,7 +701,6 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
699
701
 
700
702
  def _reindex_grid(self):
701
703
  """Reindex the grid to remove empty rows and columns."""
702
- print(f"old grid: {self.grid}")
703
704
  new_grid = []
704
705
  for row in self.grid:
705
706
  new_row = [widget for widget in row if widget is not None]
@@ -766,8 +767,8 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
766
767
 
767
768
  def clear_all(self):
768
769
  """Clear all widgets from the figure and reset to default state"""
769
- # for widget in self._widgets.values():
770
- # widget.cleanup()
770
+ for widget in self._widgets.values():
771
+ widget.cleanup()
771
772
  self.clear()
772
773
  self._widgets = defaultdict(dict)
773
774
  self.grid = []
@@ -59,6 +59,7 @@ class ImageConfig(WidgetConfig):
59
59
 
60
60
  class BECImageItem(BECConnector, pg.ImageItem):
61
61
  USER_ACCESS = [
62
+ "rpc_id",
62
63
  "config_dict",
63
64
  "set",
64
65
  "set_fft",
@@ -287,9 +288,14 @@ class BECImageItem(BECConnector, pg.ImageItem):
287
288
  else:
288
289
  raise ValueError("style should be 'simple' or 'full'")
289
290
 
291
+ def cleanup(self):
292
+ """Clean up widget."""
293
+ self.rpc_register.remove_rpc(self)
294
+
290
295
 
291
296
  class BECImageShow(BECPlotBase):
292
297
  USER_ACCESS = [
298
+ "rpc_id",
293
299
  "config_dict",
294
300
  "add_image_by_config",
295
301
  "get_image_config",
@@ -358,20 +364,6 @@ class BECImageShow(BECPlotBase):
358
364
 
359
365
  thread.start()
360
366
 
361
- def find_widget_by_id(self, item_id: str) -> BECImageItem:
362
- """
363
- Find the widget by its gui_id.
364
- Args:
365
- item_id(str): The gui_id of the widget.
366
-
367
- Returns:
368
- BECImageItem: The widget with the given gui_id.
369
- """
370
- for source, images in self._images.items():
371
- for monitor, image_item in images.items():
372
- if image_item.gui_id == item_id:
373
- return image_item
374
-
375
367
  def find_image_by_monitor(self, item_id: str) -> BECImageItem:
376
368
  """
377
369
  Find the widget by its gui_id.
@@ -719,10 +711,8 @@ class BECImageShow(BECPlotBase):
719
711
  processing_config = image_to_update.config.processing
720
712
  self.processor.set_config(processing_config)
721
713
  if self.use_threading:
722
- print("using threaded version")
723
714
  self._create_thread_worker(device, data)
724
715
  else:
725
- print("using NON-threaded version")
726
716
  data = self.processor.process_image(data)
727
717
  self.update_image(device, data)
728
718
 
@@ -809,14 +799,14 @@ class BECImageShow(BECPlotBase):
809
799
  """
810
800
  Clean up the widget.
811
801
  """
812
- print(f"Cleaning up {self.gui_id}")
813
- # for monitor in self._images["device_monitor"]:
814
- # self.bec_dispatcher.disconnect_slot(
815
- # self.on_image_update, MessageEndpoints.device_monitor(monitor)
816
- # )
817
- # if self.thread is not None and self.thread.isRunning():
818
- # self.thread.quit()
819
- # self.thread.wait()
802
+ for monitor in self._images["device_monitor"]:
803
+ self.bec_dispatcher.disconnect_slot(
804
+ self.on_image_update, MessageEndpoints.device_monitor(monitor)
805
+ )
806
+ for image in self.images:
807
+ image.cleanup()
808
+
809
+ self.rpc_register.remove_rpc(self)
820
810
 
821
811
 
822
812
  class ImageProcessor: