bec-widgets 0.82.1__py3-none-any.whl → 0.83.0__py3-none-any.whl
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.
- .gitlab-ci.yml +3 -0
- CHANGELOG.md +32 -34
- PKG-INFO +1 -1
- bec_widgets/cli/client_utils.py +5 -2
- bec_widgets/cli/server.py +24 -10
- bec_widgets/utils/bec_connector.py +2 -0
- bec_widgets/utils/bec_widget.py +6 -0
- bec_widgets/utils/generate_designer_plugin.py +6 -5
- bec_widgets/utils/reference_utils.py +92 -0
- bec_widgets/widgets/console/console.py +1 -1
- bec_widgets/widgets/device_box/__init__.py +0 -0
- bec_widgets/widgets/device_box/device_box.py +197 -0
- bec_widgets/widgets/device_box/device_box.pyproject +1 -0
- bec_widgets/widgets/device_box/device_box.ui +179 -0
- bec_widgets/widgets/device_box/device_box_plugin.py +54 -0
- bec_widgets/widgets/device_box/register_device_box.py +15 -0
- bec_widgets/widgets/figure/figure.py +3 -3
- bec_widgets/widgets/position_indicator/position_indicator.py +71 -0
- bec_widgets/widgets/position_indicator/position_indicator.pyproject +1 -0
- bec_widgets/widgets/position_indicator/position_indicator_plugin.py +54 -0
- bec_widgets/widgets/position_indicator/register_position_indicator.py +17 -0
- bec_widgets/widgets/spinner/__init__.py +0 -0
- bec_widgets/widgets/spinner/register_spinner_widget.py +15 -0
- bec_widgets/widgets/spinner/spinner.py +85 -0
- bec_widgets/widgets/spinner/spinner_widget.pyproject +1 -0
- bec_widgets/widgets/spinner/spinner_widget_plugin.py +54 -0
- bec_widgets/widgets/vscode/vscode.py +0 -14
- bec_widgets/widgets/website/website.py +1 -1
- {bec_widgets-0.82.1.dist-info → bec_widgets-0.83.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.82.1.dist-info → bec_widgets-0.83.0.dist-info}/RECORD +44 -21
- pyproject.toml +1 -1
- tests/references/SpinnerWidget/SpinnerWidget_darwin.png +0 -0
- tests/references/SpinnerWidget/SpinnerWidget_linux.png +0 -0
- tests/references/SpinnerWidget/SpinnerWidget_started_darwin.png +0 -0
- tests/references/SpinnerWidget/SpinnerWidget_started_linux.png +0 -0
- tests/unit_tests/client_mocks.py +9 -1
- tests/unit_tests/test_client_utils.py +47 -0
- tests/unit_tests/test_device_box.py +98 -0
- tests/unit_tests/test_rpc_server.py +42 -0
- tests/unit_tests/test_spinner.py +30 -0
- tests/unit_tests/test_vscode_widget.py +27 -32
- {bec_widgets-0.82.1.dist-info → bec_widgets-0.83.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.82.1.dist-info → bec_widgets-0.83.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.82.1.dist-info → bec_widgets-0.83.0.dist-info}/licenses/LICENSE +0 -0
.gitlab-ci.yml
CHANGED
CHANGELOG.md
CHANGED
@@ -1,5 +1,37 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## v0.83.0 (2024-07-08)
|
4
|
+
|
5
|
+
### Feature
|
6
|
+
|
7
|
+
* feat: added reference utils to compare renderings of widgets ([`2988fd3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2988fd387e6b8076fffec1d57e3ccab89ddb2aeb))
|
8
|
+
|
9
|
+
* feat(widgets): added device box with spinner ([`1b017ed`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1b017edfad8e78fa079210486123976695b8915c))
|
10
|
+
|
11
|
+
* feat(designer): added option to skip the widget validation for DesignerPluginGenerator ([`41bcb80`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/41bcb801674ab6c4d6069bba34ffee09c9e665db))
|
12
|
+
|
13
|
+
### Fix
|
14
|
+
|
15
|
+
* fix(terminal): added default args to avoid designer crashes on startup ([`360d171`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/360d17135573e44b80ab517756da3c0b31daab0f))
|
16
|
+
|
17
|
+
* fix(widget): fixed widget cleanup routine ([`2b29e34`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2b29e34b52d056349647bb2fcf649b749a60d292))
|
18
|
+
|
19
|
+
* fix(bec_widget): added cleanup method to bec widget base class ([`fd8766e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fd8766ed87770661da6591aeb4df5abdaf38afc7))
|
20
|
+
|
21
|
+
* fix(website): fixed dummy input ([`903ce7d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/903ce7d46b5d37d40486d0fda92d3694d3faca62))
|
22
|
+
|
23
|
+
### Test
|
24
|
+
|
25
|
+
* test(vscode): fixed vscode tests for new cleanup routine ([`eb26e2a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/eb26e2a11b229a52efe2e6d4fb28d760d3740136))
|
26
|
+
|
27
|
+
* test(vscode): improved vscode test ([`5de8804`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5de8804da1e41eafad2472344904b3324438c13b))
|
28
|
+
|
29
|
+
## v0.82.2 (2024-07-08)
|
30
|
+
|
31
|
+
### Fix
|
32
|
+
|
33
|
+
* fix(rpc_server): pass cli config to server ([`90178e2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/90178e2f61fa9dac7d82c0d0db40a9767bb133e6))
|
34
|
+
|
3
35
|
## v0.82.1 (2024-07-07)
|
4
36
|
|
5
37
|
### Fix
|
@@ -111,37 +143,3 @@
|
|
111
143
|
* fix(toolbar): change default color to black to match BECFigure theme ([`b8774e0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b8774e0b0bc43dcd00f94f42539a778e507ca27d))
|
112
144
|
|
113
145
|
* fix(motor_map): fixed bug with residual trace after changing motors ([`aaa0d10`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/aaa0d1003d2e94b45bafe4f700852c2c05288aea))
|
114
|
-
|
115
|
-
* fix(widget_io): widget handler adjusted for spinboxes and comboboxes ([`3dc0532`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3dc0532df05b6ec0a2522107fa0b1e210ce7d91b))
|
116
|
-
|
117
|
-
### Refactor
|
118
|
-
|
119
|
-
* refactor(toolbar): cleanup and adjusted colors ([`96863ad`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/96863adf53c15112645d20eb6200733617801c6d))
|
120
|
-
|
121
|
-
## v0.78.1 (2024-07-02)
|
122
|
-
|
123
|
-
### Fix
|
124
|
-
|
125
|
-
* fix(ui_loader): ui loader is compatible with bec plugins ([`b787759`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b787759f44486dc7af2c03811efb156041e4b6cb))
|
126
|
-
|
127
|
-
## v0.78.0 (2024-07-02)
|
128
|
-
|
129
|
-
### Feature
|
130
|
-
|
131
|
-
* feat(color_button): patched ColorButton from pyqtgraph to be able to be opened in another QDialog ([`c36bb80`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c36bb80d6a4939802a4a1c8e5452c7b94bac185e))
|
132
|
-
|
133
|
-
## v0.77.0 (2024-07-02)
|
134
|
-
|
135
|
-
### Feature
|
136
|
-
|
137
|
-
* feat(bec_connector): export config to yaml ([`a391f30`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a391f3018c50fee6a4a06884491b957df80c3cd3))
|
138
|
-
|
139
|
-
### Fix
|
140
|
-
|
141
|
-
* fix(waveform): scatter 2D brush error ([`215d59c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/215d59c8bfe7fda9aff8cec8353bef9e1ce2eca1))
|
142
|
-
|
143
|
-
* fix(figure): API cleanup ([`008a33a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/008a33a9b192473cc58e90cd6d98c5bcb5f7b8c0))
|
144
|
-
|
145
|
-
* fix(figure): if/else logic corrected in subplot_factory ([`3e78723`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3e787234c7274b0698423d7bf9a4c54ec46bad5f))
|
146
|
-
|
147
|
-
* fix(image): processing of already displayed data; closes #106 ([`1173510`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1173510105d2d70d7e498c2ac1e122cea3a16597))
|
PKG-INFO
CHANGED
bec_widgets/cli/client_utils.py
CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import importlib
|
4
4
|
import importlib.metadata as imd
|
5
|
+
import json
|
5
6
|
import os
|
6
7
|
import select
|
7
8
|
import subprocess
|
@@ -87,7 +88,7 @@ def _get_output(process, logger) -> None:
|
|
87
88
|
print(f"Error reading process output: {str(e)}")
|
88
89
|
|
89
90
|
|
90
|
-
def _start_plot_process(gui_id, gui_class, config, logger=None) -> None:
|
91
|
+
def _start_plot_process(gui_id: str, gui_class: type, config: dict | str, logger=None) -> None:
|
91
92
|
"""
|
92
93
|
Start the plot in a new process.
|
93
94
|
|
@@ -98,6 +99,8 @@ def _start_plot_process(gui_id, gui_class, config, logger=None) -> None:
|
|
98
99
|
# pylint: disable=subprocess-run-check
|
99
100
|
command = ["bec-gui-server", "--id", gui_id, "--gui_class", gui_class.__name__]
|
100
101
|
if config:
|
102
|
+
if isinstance(config, dict):
|
103
|
+
config = json.dumps(config)
|
101
104
|
command.extend(["--config", config])
|
102
105
|
|
103
106
|
env_dict = os.environ.copy()
|
@@ -190,7 +193,7 @@ class BECGuiClientMixin:
|
|
190
193
|
if self._process is None or self._process.poll() is not None:
|
191
194
|
self._start_update_script()
|
192
195
|
self._process, self._process_output_processing_thread = _start_plot_process(
|
193
|
-
self._gui_id, self.__class__, self._client._service_config.
|
196
|
+
self._gui_id, self.__class__, self._client._service_config.config
|
194
197
|
)
|
195
198
|
while not self.gui_is_alive():
|
196
199
|
print("Waiting for GUI to start...")
|
bec_widgets/cli/server.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import inspect
|
4
|
+
import json
|
4
5
|
import signal
|
5
6
|
import sys
|
6
7
|
from contextlib import redirect_stderr, redirect_stdout
|
@@ -141,10 +142,30 @@ class SimpleFileLikeFromLogOutputFunc:
|
|
141
142
|
return
|
142
143
|
|
143
144
|
|
145
|
+
def _start_server(gui_id: str, gui_class: Union[BECFigure, BECDockArea], config: str | None = None):
|
146
|
+
if config:
|
147
|
+
try:
|
148
|
+
config = json.loads(config)
|
149
|
+
service_config = ServiceConfig(config=config)
|
150
|
+
except (json.JSONDecodeError, TypeError):
|
151
|
+
service_config = ServiceConfig(config_path=config)
|
152
|
+
else:
|
153
|
+
# if no config is provided, use the default config
|
154
|
+
service_config = ServiceConfig()
|
155
|
+
|
156
|
+
bec_logger.configure(
|
157
|
+
service_config.redis,
|
158
|
+
QtRedisConnector,
|
159
|
+
service_name="BECWidgetsCLIServer",
|
160
|
+
service_config=service_config.service_config,
|
161
|
+
)
|
162
|
+
server = BECWidgetsCLIServer(gui_id=gui_id, config=service_config, gui_class=gui_class)
|
163
|
+
return server
|
164
|
+
|
165
|
+
|
144
166
|
def main():
|
145
167
|
import argparse
|
146
168
|
import os
|
147
|
-
import sys
|
148
169
|
|
149
170
|
from qtpy.QtCore import QSize
|
150
171
|
from qtpy.QtGui import QIcon
|
@@ -159,7 +180,7 @@ def main():
|
|
159
180
|
type=str,
|
160
181
|
help="Name of the gui class to be rendered. Possible values: \n- BECFigure\n- BECDockArea",
|
161
182
|
)
|
162
|
-
parser.add_argument("--config", type=str, help="Config file")
|
183
|
+
parser.add_argument("--config", type=str, help="Config file or config string.")
|
163
184
|
|
164
185
|
args = parser.parse_args()
|
165
186
|
|
@@ -188,14 +209,7 @@ def main():
|
|
188
209
|
win = QMainWindow()
|
189
210
|
win.setWindowTitle("BEC Widgets")
|
190
211
|
|
191
|
-
|
192
|
-
bec_logger.configure(
|
193
|
-
service_config.redis,
|
194
|
-
QtRedisConnector,
|
195
|
-
service_name="BECWidgetsCLIServer",
|
196
|
-
service_config=service_config.service_config,
|
197
|
-
)
|
198
|
-
server = BECWidgetsCLIServer(gui_id=args.id, config=service_config, gui_class=gui_class)
|
212
|
+
server = _start_server(args.id, gui_class, args.config)
|
199
213
|
|
200
214
|
gui = server.gui
|
201
215
|
win.setCentralWidget(gui)
|
@@ -289,6 +289,8 @@ class BECConnector(BECWidget):
|
|
289
289
|
print("No more connections. Shutting down GUI BEC client.")
|
290
290
|
self.bec_dispatcher.disconnect_all()
|
291
291
|
self.client.shutdown()
|
292
|
+
if hasattr(super(), "cleanup"):
|
293
|
+
super().cleanup()
|
292
294
|
|
293
295
|
# def closeEvent(self, event):
|
294
296
|
# self.cleanup()
|
bec_widgets/utils/bec_widget.py
CHANGED
@@ -58,11 +58,12 @@ class DesignerPluginGenerator:
|
|
58
58
|
os.path.dirname(os.path.abspath(__file__)), "plugin_templates"
|
59
59
|
)
|
60
60
|
|
61
|
-
def run(self):
|
61
|
+
def run(self, validate=True):
|
62
62
|
if self._excluded:
|
63
63
|
print(f"Plugin {self.widget.__name__} is excluded from generation.")
|
64
64
|
return
|
65
|
-
|
65
|
+
if validate:
|
66
|
+
self._check_class_validity()
|
66
67
|
self._load_templates()
|
67
68
|
self._write_templates()
|
68
69
|
|
@@ -142,7 +143,7 @@ class DesignerPluginGenerator:
|
|
142
143
|
|
143
144
|
if __name__ == "__main__": # pragma: no cover
|
144
145
|
# from bec_widgets.widgets.bec_queue.bec_queue import BECQueue
|
145
|
-
from bec_widgets.widgets.
|
146
|
+
from bec_widgets.widgets.spinner.spinner import SpinnerWidget
|
146
147
|
|
147
|
-
generator = DesignerPluginGenerator(
|
148
|
-
generator.run()
|
148
|
+
generator = DesignerPluginGenerator(SpinnerWidget)
|
149
|
+
generator.run(validate=False)
|
@@ -0,0 +1,92 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
|
4
|
+
from PIL import Image, ImageChops
|
5
|
+
from qtpy.QtGui import QPixmap
|
6
|
+
|
7
|
+
import bec_widgets
|
8
|
+
|
9
|
+
REFERENCE_DIR = os.path.join(
|
10
|
+
os.path.dirname(os.path.dirname(bec_widgets.__file__)), "tests/references"
|
11
|
+
)
|
12
|
+
REFERENCE_DIR_FAILURES = os.path.join(
|
13
|
+
os.path.dirname(os.path.dirname(bec_widgets.__file__)), "tests/reference_failures"
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
def compare_images(image1_path: str, reference_image_path: str):
|
18
|
+
"""
|
19
|
+
Load two images and compare them pixel by pixel
|
20
|
+
|
21
|
+
Args:
|
22
|
+
image1_path(str): The path to the first image
|
23
|
+
reference_image_path(str): The path to the reference image
|
24
|
+
|
25
|
+
Raises:
|
26
|
+
ValueError: If the images are different
|
27
|
+
"""
|
28
|
+
image1 = Image.open(image1_path)
|
29
|
+
image2 = Image.open(reference_image_path)
|
30
|
+
if image1.size != image2.size:
|
31
|
+
raise ValueError("Image size has changed")
|
32
|
+
diff = ImageChops.difference(image1, image2)
|
33
|
+
if diff.getbbox():
|
34
|
+
# copy image1 to the reference directory to upload as artifact
|
35
|
+
os.makedirs(REFERENCE_DIR_FAILURES, exist_ok=True)
|
36
|
+
image_name = os.path.join(REFERENCE_DIR_FAILURES, os.path.basename(image1_path))
|
37
|
+
image1.save(image_name)
|
38
|
+
print(f"Image saved to {image_name}")
|
39
|
+
|
40
|
+
raise ValueError("Images are different")
|
41
|
+
|
42
|
+
|
43
|
+
def snap_and_compare(widget: any, output_directory: str, suffix: str = ""):
|
44
|
+
"""
|
45
|
+
Save a rendering of a widget and compare it to a reference image
|
46
|
+
|
47
|
+
Args:
|
48
|
+
widget(any): The widget to render
|
49
|
+
output_directory(str): The directory to save the image to
|
50
|
+
suffix(str): A suffix to append to the image name
|
51
|
+
|
52
|
+
Raises:
|
53
|
+
ValueError: If the images are different
|
54
|
+
|
55
|
+
Examples:
|
56
|
+
snap_and_compare(widget, tmpdir, suffix="started")
|
57
|
+
|
58
|
+
"""
|
59
|
+
|
60
|
+
if not isinstance(output_directory, str):
|
61
|
+
output_directory = str(output_directory)
|
62
|
+
|
63
|
+
os_suffix = sys.platform
|
64
|
+
|
65
|
+
name = (
|
66
|
+
f"{widget.__class__.__name__}_{suffix}_{os_suffix}.png"
|
67
|
+
if suffix
|
68
|
+
else f"{widget.__class__.__name__}_{os_suffix}.png"
|
69
|
+
)
|
70
|
+
|
71
|
+
# Save the widget to a pixmap
|
72
|
+
test_image_path = os.path.join(output_directory, name)
|
73
|
+
pixmap = QPixmap(widget.size())
|
74
|
+
widget.render(pixmap)
|
75
|
+
pixmap.save(test_image_path)
|
76
|
+
|
77
|
+
try:
|
78
|
+
reference_path = os.path.join(REFERENCE_DIR, f"{widget.__class__.__name__}")
|
79
|
+
reference_image_path = os.path.join(reference_path, name)
|
80
|
+
|
81
|
+
if not os.path.exists(reference_image_path):
|
82
|
+
raise ValueError(f"Reference image not found: {reference_image_path}")
|
83
|
+
|
84
|
+
compare_images(test_image_path, reference_image_path)
|
85
|
+
|
86
|
+
except ValueError:
|
87
|
+
image = Image.open(test_image_path)
|
88
|
+
os.makedirs(REFERENCE_DIR_FAILURES, exist_ok=True)
|
89
|
+
image_name = os.path.join(REFERENCE_DIR_FAILURES, name)
|
90
|
+
image.save(image_name)
|
91
|
+
print(f"Image saved to {image_name}")
|
92
|
+
raise
|
@@ -222,7 +222,7 @@ class _TerminalWidget(QtWidgets.QPlainTextEdit):
|
|
222
222
|
Start ``Backend`` process and render Pyte output as text.
|
223
223
|
"""
|
224
224
|
|
225
|
-
def __init__(self, parent, numColumns, numLines, **kwargs):
|
225
|
+
def __init__(self, parent, numColumns=125, numLines=50, **kwargs):
|
226
226
|
super().__init__(parent)
|
227
227
|
|
228
228
|
# file descriptor to communicate with the subprocess
|
File without changes
|
@@ -0,0 +1,197 @@
|
|
1
|
+
import os
|
2
|
+
import uuid
|
3
|
+
|
4
|
+
from bec_lib.endpoints import MessageEndpoints
|
5
|
+
from bec_lib.messages import ScanQueueMessage
|
6
|
+
from qtpy.QtCore import Property, Signal, Slot
|
7
|
+
from qtpy.QtGui import QDoubleValidator
|
8
|
+
from qtpy.QtWidgets import QDoubleSpinBox, QVBoxLayout, QWidget
|
9
|
+
|
10
|
+
from bec_widgets.utils import UILoader
|
11
|
+
from bec_widgets.utils.bec_connector import BECConnector
|
12
|
+
|
13
|
+
|
14
|
+
class DeviceBox(BECConnector, QWidget):
|
15
|
+
device_changed = Signal(str, str)
|
16
|
+
|
17
|
+
def __init__(self, parent=None, device=None, *args, **kwargs):
|
18
|
+
super().__init__(*args, **kwargs)
|
19
|
+
QWidget.__init__(self, parent=parent)
|
20
|
+
self.get_bec_shortcuts()
|
21
|
+
self._device = ""
|
22
|
+
self._limits = None
|
23
|
+
|
24
|
+
self.init_ui()
|
25
|
+
|
26
|
+
if device is not None:
|
27
|
+
self.device = device
|
28
|
+
self.init_device()
|
29
|
+
|
30
|
+
def init_ui(self):
|
31
|
+
self.device_changed.connect(self.on_device_change)
|
32
|
+
|
33
|
+
current_path = os.path.dirname(__file__)
|
34
|
+
self.ui = UILoader(self).loader(os.path.join(current_path, "device_box.ui"))
|
35
|
+
|
36
|
+
self.layout = QVBoxLayout(self)
|
37
|
+
self.layout.addWidget(self.ui)
|
38
|
+
self.layout.setSpacing(0)
|
39
|
+
self.layout.setContentsMargins(0, 0, 0, 0)
|
40
|
+
|
41
|
+
# fix the size of the device box
|
42
|
+
db = self.ui.device_box
|
43
|
+
db.setFixedHeight(234)
|
44
|
+
db.setFixedWidth(224)
|
45
|
+
|
46
|
+
self.ui.step_size.setStepType(QDoubleSpinBox.AdaptiveDecimalStepType)
|
47
|
+
self.ui.stop.clicked.connect(self.on_stop)
|
48
|
+
self.ui.tweak_right.clicked.connect(self.on_tweak_right)
|
49
|
+
self.ui.tweak_right.setToolTip("Tweak right")
|
50
|
+
self.ui.tweak_left.clicked.connect(self.on_tweak_left)
|
51
|
+
self.ui.tweak_left.setToolTip("Tweak left")
|
52
|
+
self.ui.setpoint.returnPressed.connect(self.on_setpoint_change)
|
53
|
+
|
54
|
+
self.setpoint_validator = QDoubleValidator()
|
55
|
+
self.ui.setpoint.setValidator(self.setpoint_validator)
|
56
|
+
self.ui.spinner_widget.start()
|
57
|
+
|
58
|
+
def init_device(self):
|
59
|
+
if self.device in self.dev:
|
60
|
+
data = self.dev[self.device].read()
|
61
|
+
self.on_device_readback({"signals": data}, {})
|
62
|
+
|
63
|
+
@Property(str)
|
64
|
+
def device(self):
|
65
|
+
return self._device
|
66
|
+
|
67
|
+
@device.setter
|
68
|
+
def device(self, value):
|
69
|
+
if not value or not isinstance(value, str):
|
70
|
+
return
|
71
|
+
old_device = self._device
|
72
|
+
self._device = value
|
73
|
+
self.device_changed.emit(old_device, value)
|
74
|
+
|
75
|
+
@Slot(str, str)
|
76
|
+
def on_device_change(self, old_device: str, new_device: str):
|
77
|
+
if new_device not in self.dev:
|
78
|
+
print(f"Device {new_device} not found in the device list")
|
79
|
+
return
|
80
|
+
print(f"Device changed from {old_device} to {new_device}")
|
81
|
+
self.init_device()
|
82
|
+
self.bec_dispatcher.disconnect_slot(
|
83
|
+
self.on_device_readback, MessageEndpoints.device_readback(old_device)
|
84
|
+
)
|
85
|
+
self.bec_dispatcher.connect_slot(
|
86
|
+
self.on_device_readback, MessageEndpoints.device_readback(new_device)
|
87
|
+
)
|
88
|
+
self.ui.device_box.setTitle(new_device)
|
89
|
+
self.ui.readback.setToolTip(f"{self.device} readback")
|
90
|
+
self.ui.setpoint.setToolTip(f"{self.device} setpoint")
|
91
|
+
self.ui.step_size.setToolTip(f"Step size for {new_device}")
|
92
|
+
|
93
|
+
precision = self.dev[new_device].precision
|
94
|
+
if precision is not None:
|
95
|
+
self.ui.step_size.setDecimals(precision)
|
96
|
+
self.ui.step_size.setValue(10**-precision * 10)
|
97
|
+
|
98
|
+
@Slot(dict, dict)
|
99
|
+
def on_device_readback(self, msg_content: dict, metadata: dict):
|
100
|
+
signals = msg_content.get("signals", {})
|
101
|
+
# pylint: disable=protected-access
|
102
|
+
hinted_signals = self.dev[self.device]._hints
|
103
|
+
precision = self.dev[self.device].precision
|
104
|
+
|
105
|
+
readback_val = None
|
106
|
+
setpoint_val = None
|
107
|
+
|
108
|
+
if len(hinted_signals) == 1:
|
109
|
+
signal = hinted_signals[0]
|
110
|
+
readback_val = signals.get(signal, {}).get("value")
|
111
|
+
|
112
|
+
if f"{self.device}_setpoint" in signals:
|
113
|
+
setpoint_val = signals.get(f"{self.device}_setpoint", {}).get("value")
|
114
|
+
|
115
|
+
if f"{self.device}_motor_is_moving" in signals:
|
116
|
+
is_moving = signals.get(f"{self.device}_motor_is_moving", {}).get("value")
|
117
|
+
if is_moving:
|
118
|
+
self.ui.spinner_widget.start()
|
119
|
+
self.ui.spinner_widget.setToolTip("Device is moving")
|
120
|
+
else:
|
121
|
+
self.ui.spinner_widget.stop()
|
122
|
+
self.ui.spinner_widget.setToolTip("Device is idle")
|
123
|
+
|
124
|
+
if readback_val is not None:
|
125
|
+
self.ui.readback.setText(f"{readback_val:.{precision}f}")
|
126
|
+
|
127
|
+
if setpoint_val is not None:
|
128
|
+
self.ui.setpoint.setText(f"{setpoint_val:.{precision}f}")
|
129
|
+
|
130
|
+
limits = self.dev[self.device].limits
|
131
|
+
self.update_limits(limits)
|
132
|
+
if limits is not None and readback_val is not None and limits[0] != limits[1]:
|
133
|
+
pos = (readback_val - limits[0]) / (limits[1] - limits[0])
|
134
|
+
self.ui.position_indicator.on_position_update(pos)
|
135
|
+
|
136
|
+
def update_limits(self, limits):
|
137
|
+
if limits == self._limits:
|
138
|
+
return
|
139
|
+
self._limits = limits
|
140
|
+
if limits is not None and limits[0] != limits[1]:
|
141
|
+
self.ui.position_indicator.setToolTip(f"Min: {limits[0]}, Max: {limits[1]}")
|
142
|
+
self.setpoint_validator.setRange(limits[0], limits[1])
|
143
|
+
else:
|
144
|
+
self.ui.position_indicator.setToolTip("No limits set")
|
145
|
+
self.setpoint_validator.setRange(float("-inf"), float("inf"))
|
146
|
+
|
147
|
+
@Slot()
|
148
|
+
def on_stop(self):
|
149
|
+
request_id = str(uuid.uuid4())
|
150
|
+
params = {
|
151
|
+
"device": self.device,
|
152
|
+
"rpc_id": request_id,
|
153
|
+
"func": "stop",
|
154
|
+
"args": [],
|
155
|
+
"kwargs": {},
|
156
|
+
}
|
157
|
+
msg = ScanQueueMessage(
|
158
|
+
scan_type="device_rpc",
|
159
|
+
parameter=params,
|
160
|
+
queue="emergency",
|
161
|
+
metadata={"RID": request_id, "response": False},
|
162
|
+
)
|
163
|
+
self.client.connector.send(MessageEndpoints.scan_queue_request(), msg)
|
164
|
+
|
165
|
+
@property
|
166
|
+
def step_size(self):
|
167
|
+
return self.ui.step_size.value()
|
168
|
+
|
169
|
+
@Slot()
|
170
|
+
def on_tweak_right(self):
|
171
|
+
self.dev[self.device].move(self.step_size, relative=True)
|
172
|
+
|
173
|
+
@Slot()
|
174
|
+
def on_tweak_left(self):
|
175
|
+
self.dev[self.device].move(-self.step_size, relative=True)
|
176
|
+
|
177
|
+
@Slot()
|
178
|
+
def on_setpoint_change(self):
|
179
|
+
self.ui.setpoint.clearFocus()
|
180
|
+
setpoint = self.ui.setpoint.text()
|
181
|
+
self.dev[self.device].move(float(setpoint), relative=False)
|
182
|
+
self.ui.tweak_left.setToolTip(f"Tweak left by {self.step_size}")
|
183
|
+
self.ui.tweak_right.setToolTip(f"Tweak right by {self.step_size}")
|
184
|
+
|
185
|
+
|
186
|
+
if __name__ == "__main__": # pragma: no cover
|
187
|
+
import sys
|
188
|
+
|
189
|
+
import qdarktheme
|
190
|
+
from qtpy.QtWidgets import QApplication
|
191
|
+
|
192
|
+
app = QApplication(sys.argv)
|
193
|
+
qdarktheme.setup_theme("light")
|
194
|
+
widget = DeviceBox(device="samx")
|
195
|
+
|
196
|
+
widget.show()
|
197
|
+
sys.exit(app.exec_())
|
@@ -0,0 +1 @@
|
|
1
|
+
{'files': ['device_box.py']}
|