bec-ipython-client 3.64.1__py3-none-any.whl → 3.86.1__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.
Potentially problematic release.
This version of bec-ipython-client might be problematic. Click here for more details.
- .gitignore +3 -0
- PKG-INFO +2 -2
- bec_ipython_client/callbacks/device_progress.py +11 -6
- bec_ipython_client/callbacks/ipython_live_updates.py +47 -19
- bec_ipython_client/callbacks/live_table.py +36 -9
- bec_ipython_client/callbacks/move_device.py +121 -59
- bec_ipython_client/callbacks/utils.py +5 -23
- bec_ipython_client/main.py +77 -7
- bec_ipython_client/signals.py +9 -3
- {bec_ipython_client-3.64.1.dist-info → bec_ipython_client-3.86.1.dist-info}/METADATA +2 -2
- bec_ipython_client-3.86.1.dist-info/RECORD +44 -0
- {bec_ipython_client-3.64.1.dist-info → bec_ipython_client-3.86.1.dist-info}/WHEEL +1 -1
- demo.py +2 -1
- pyproject.toml +2 -2
- tests/client_tests/conftest.py +19 -0
- tests/client_tests/test_bec_client.py +32 -1
- tests/client_tests/test_ipython_live_updates.py +259 -68
- tests/client_tests/test_live_table.py +33 -12
- tests/client_tests/test_move_callback.py +112 -70
- tests/end-2-end/_ensure_requirements_container.py +21 -0
- tests/end-2-end/test_procedures_e2e.py +26 -17
- tests/end-2-end/test_scans_e2e.py +19 -13
- tests/end-2-end/test_scans_lib_e2e.py +23 -19
- bec_ipython_client-3.64.1.dist-info/RECORD +0 -42
- {bec_ipython_client-3.64.1.dist-info → bec_ipython_client-3.86.1.dist-info}/entry_points.txt +0 -0
.gitignore
CHANGED
PKG-INFO
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bec_ipython_client
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.86.1
|
|
4
4
|
Summary: BEC IPython client
|
|
5
5
|
Project-URL: Bug Tracker, https://github.com/bec-project/bec/issues
|
|
6
6
|
Project-URL: Homepage, https://github.com/bec-project/bec
|
|
7
7
|
Classifier: Development Status :: 3 - Alpha
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Topic :: Scientific/Engineering
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
11
|
Requires-Dist: bec-lib~=3.0
|
|
12
12
|
Requires-Dist: ipython~=8.22
|
|
13
13
|
Requires-Dist: numpy<3.0,>=1.24
|
|
@@ -14,11 +14,16 @@ class LiveUpdatesDeviceProgress(LiveUpdatesTable):
|
|
|
14
14
|
|
|
15
15
|
REPORT_TYPE = "device_progress"
|
|
16
16
|
|
|
17
|
-
def
|
|
17
|
+
def core(self):
|
|
18
|
+
"""core function to run the live updates for the table"""
|
|
19
|
+
self._wait_for_report_instructions()
|
|
20
|
+
self._run_update(self.report_instruction[self.REPORT_TYPE])
|
|
21
|
+
|
|
22
|
+
def _run_update(self, device_names: list[str]):
|
|
18
23
|
"""Run the update loop for the progress bar.
|
|
19
24
|
|
|
20
25
|
Args:
|
|
21
|
-
device_names (str): The name of the device to monitor.
|
|
26
|
+
device_names (list[str]): The name of the device to monitor.
|
|
22
27
|
"""
|
|
23
28
|
with ScanProgressBar(
|
|
24
29
|
scan_number=self.scan_item.scan_number, clear_on_exit=False
|
|
@@ -29,7 +34,7 @@ class LiveUpdatesDeviceProgress(LiveUpdatesTable):
|
|
|
29
34
|
self._print_client_msgs_asap()
|
|
30
35
|
self._print_client_msgs_all()
|
|
31
36
|
|
|
32
|
-
def _update_progressbar(self, progressbar: ScanProgressBar, device_names: str) -> bool:
|
|
37
|
+
def _update_progressbar(self, progressbar: ScanProgressBar, device_names: list[str]) -> bool:
|
|
33
38
|
"""Update the progressbar based on the device status message
|
|
34
39
|
|
|
35
40
|
Args:
|
|
@@ -41,17 +46,17 @@ class LiveUpdatesDeviceProgress(LiveUpdatesTable):
|
|
|
41
46
|
self.check_alarms()
|
|
42
47
|
status = self.bec.connector.get(MessageEndpoints.device_progress(device_names[0]))
|
|
43
48
|
if not status:
|
|
44
|
-
logger.
|
|
49
|
+
logger.trace("waiting for new data point")
|
|
45
50
|
time.sleep(0.1)
|
|
46
51
|
return False
|
|
47
52
|
if status.metadata.get("scan_id") != self.scan_item.scan_id:
|
|
48
|
-
logger.
|
|
53
|
+
logger.trace("waiting for new data point")
|
|
49
54
|
time.sleep(0.1)
|
|
50
55
|
return False
|
|
51
56
|
|
|
52
57
|
point_id = status.content.get("value")
|
|
53
58
|
if point_id is None:
|
|
54
|
-
logger.
|
|
59
|
+
logger.trace("waiting for new data point")
|
|
55
60
|
time.sleep(0.1)
|
|
56
61
|
return False
|
|
57
62
|
|
|
@@ -31,7 +31,7 @@ class IPythonLiveUpdates:
|
|
|
31
31
|
self._interrupted_request = None
|
|
32
32
|
self._active_callback = None
|
|
33
33
|
self._processed_instructions = 0
|
|
34
|
-
self._active_request = None
|
|
34
|
+
self._active_request: messages.ScanQueueMessage | None = None
|
|
35
35
|
self._user_callback = None
|
|
36
36
|
self._request_block_index = collections.defaultdict(lambda: 0)
|
|
37
37
|
self._request_block_id = None
|
|
@@ -47,9 +47,11 @@ class IPythonLiveUpdates:
|
|
|
47
47
|
Args:
|
|
48
48
|
report_instructions (list): The list of report instructions.
|
|
49
49
|
"""
|
|
50
|
-
|
|
50
|
+
if not self._active_request:
|
|
51
|
+
return
|
|
52
|
+
scan_type = self._active_request.scan_type
|
|
51
53
|
if scan_type in ["open_scan_def", "close_scan_def"]:
|
|
52
|
-
self._process_instruction({"scan_progress": 0})
|
|
54
|
+
self._process_instruction({"scan_progress": {"points": 0, "show_table": True}})
|
|
53
55
|
return
|
|
54
56
|
if scan_type == "close_scan_group":
|
|
55
57
|
return
|
|
@@ -74,6 +76,9 @@ class IPythonLiveUpdates:
|
|
|
74
76
|
scan_report_type = list(instr.keys())[0]
|
|
75
77
|
scan_def_id = self.client.scans._scan_def_id
|
|
76
78
|
interactive_scan = self.client.scans._interactive_scan
|
|
79
|
+
if self._active_request is None:
|
|
80
|
+
# Already checked in caller method. It is just for type checking purposes.
|
|
81
|
+
return
|
|
77
82
|
if scan_def_id is None or interactive_scan:
|
|
78
83
|
if scan_report_type == "readback":
|
|
79
84
|
LiveUpdatesReadbackProgressbar(
|
|
@@ -126,17 +131,22 @@ class IPythonLiveUpdates:
|
|
|
126
131
|
)
|
|
127
132
|
self._active_callback.run()
|
|
128
133
|
|
|
129
|
-
def _available_req_blocks(
|
|
134
|
+
def _available_req_blocks(
|
|
135
|
+
self, queue: QueueItem, request: messages.ScanQueueMessage
|
|
136
|
+
) -> list[messages.RequestBlock]:
|
|
130
137
|
"""Get the available request blocks.
|
|
131
138
|
|
|
132
139
|
Args:
|
|
133
140
|
queue (QueueItem): The queue item.
|
|
134
141
|
request (messages.ScanQueueMessage): The request message.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
list[messages.RequestBlock]: The list of available request blocks.
|
|
135
145
|
"""
|
|
136
146
|
available_blocks = [
|
|
137
147
|
req_block
|
|
138
148
|
for req_block in queue.request_blocks
|
|
139
|
-
if req_block
|
|
149
|
+
if req_block.RID == request.metadata["RID"]
|
|
140
150
|
]
|
|
141
151
|
return available_blocks
|
|
142
152
|
|
|
@@ -151,11 +161,14 @@ class IPythonLiveUpdates:
|
|
|
151
161
|
scan_request = ScanRequestMixin(self.client, request.metadata["RID"])
|
|
152
162
|
scan_request.wait()
|
|
153
163
|
|
|
164
|
+
# After .wait, we can be sure that the queue item is available, so we can
|
|
165
|
+
assert scan_request.scan_queue_request is not None
|
|
166
|
+
|
|
154
167
|
# get the corresponding queue item
|
|
155
|
-
while not scan_request.
|
|
168
|
+
while not scan_request.scan_queue_request.queue:
|
|
156
169
|
time.sleep(0.01)
|
|
157
170
|
|
|
158
|
-
self._current_queue = queue = scan_request.
|
|
171
|
+
self._current_queue = queue = scan_request.scan_queue_request.queue
|
|
159
172
|
self._request_block_id = req_id = self._active_request.metadata.get("RID")
|
|
160
173
|
|
|
161
174
|
while queue.status not in ["COMPLETED", "ABORTED", "HALTED"]:
|
|
@@ -164,7 +177,7 @@ class IPythonLiveUpdates:
|
|
|
164
177
|
|
|
165
178
|
available_blocks = self._available_req_blocks(queue, request)
|
|
166
179
|
req_block = available_blocks[self._request_block_index[req_id]]
|
|
167
|
-
report_instructions = req_block.
|
|
180
|
+
report_instructions = req_block.report_instructions or []
|
|
168
181
|
self._process_report_instructions(report_instructions)
|
|
169
182
|
|
|
170
183
|
self._reset()
|
|
@@ -188,12 +201,19 @@ class IPythonLiveUpdates:
|
|
|
188
201
|
self.client.queue.request_scan_halt()
|
|
189
202
|
|
|
190
203
|
def _element_in_queue(self) -> bool:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
)
|
|
194
|
-
if not queue:
|
|
204
|
+
if self.client.queue is None:
|
|
205
|
+
return False
|
|
206
|
+
if (csq := self.client.queue.queue_storage.current_scan_queue) is None:
|
|
195
207
|
return False
|
|
196
|
-
|
|
208
|
+
scan_queue_status = csq.get("primary")
|
|
209
|
+
if scan_queue_status is None:
|
|
210
|
+
return False
|
|
211
|
+
queue_info = scan_queue_status.info
|
|
212
|
+
if not queue_info:
|
|
213
|
+
return False
|
|
214
|
+
if self._current_queue is None:
|
|
215
|
+
return False
|
|
216
|
+
return self._current_queue.queue_id == queue_info[0].queue_id
|
|
197
217
|
|
|
198
218
|
def _process_queue(
|
|
199
219
|
self, queue: QueueItem, request: messages.ScanQueueMessage, req_id: str
|
|
@@ -213,9 +233,11 @@ class IPythonLiveUpdates:
|
|
|
213
233
|
if not queue.request_blocks or not queue.status or queue.queue_position is None:
|
|
214
234
|
return False
|
|
215
235
|
if queue.status == "PENDING" and queue.queue_position > 0:
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
236
|
+
primary_queue = self.client.queue.queue_storage.current_scan_queue.get("primary")
|
|
237
|
+
|
|
238
|
+
if primary_queue is None:
|
|
239
|
+
return False
|
|
240
|
+
status = primary_queue.status
|
|
219
241
|
print(
|
|
220
242
|
"Scan is enqueued and is waiting for execution. Current position in queue:"
|
|
221
243
|
f" {queue.queue_position + 1}. Queue status: {status}.",
|
|
@@ -223,14 +245,16 @@ class IPythonLiveUpdates:
|
|
|
223
245
|
flush=True,
|
|
224
246
|
)
|
|
225
247
|
available_blocks = self._available_req_blocks(queue, request)
|
|
248
|
+
if not available_blocks:
|
|
249
|
+
return False
|
|
226
250
|
req_block = available_blocks[self._request_block_index[req_id]]
|
|
227
|
-
if req_block
|
|
251
|
+
if req_block.msg.scan_type in [
|
|
228
252
|
"open_scan_def",
|
|
229
253
|
"mv",
|
|
230
254
|
]: # TODO: make this more general for all scan types that don't have report instructions
|
|
231
255
|
return True
|
|
232
256
|
|
|
233
|
-
report_instructions = req_block[
|
|
257
|
+
report_instructions = req_block.report_instructions or []
|
|
234
258
|
if not report_instructions:
|
|
235
259
|
return False
|
|
236
260
|
self._process_report_instructions(report_instructions)
|
|
@@ -258,7 +282,11 @@ class IPythonLiveUpdates:
|
|
|
258
282
|
self._current_queue = None
|
|
259
283
|
self._user_callback = None
|
|
260
284
|
self._processed_instructions = 0
|
|
261
|
-
scan_closed =
|
|
285
|
+
scan_closed = (
|
|
286
|
+
forced
|
|
287
|
+
or self._active_request is None
|
|
288
|
+
or (self._active_request.scan_type == "close_scan_def")
|
|
289
|
+
)
|
|
262
290
|
self._active_request = None
|
|
263
291
|
|
|
264
292
|
if self.client.scans._scan_def_id and not scan_closed:
|
|
@@ -56,7 +56,6 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
56
56
|
super().__init__(
|
|
57
57
|
bec, report_instruction=report_instruction, request=request, callbacks=callbacks
|
|
58
58
|
)
|
|
59
|
-
self.scan_queue_request = None
|
|
60
59
|
self.scan_item = None
|
|
61
60
|
self.dev_values = None
|
|
62
61
|
self.point_data = None
|
|
@@ -73,12 +72,14 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
73
72
|
def wait_for_scan_to_start(self):
|
|
74
73
|
"""wait until the scan starts"""
|
|
75
74
|
while True:
|
|
75
|
+
if not self.scan_item or not self.scan_item.queue:
|
|
76
|
+
raise RuntimeError("No scan item or scan queue available.")
|
|
76
77
|
queue_pos = self.scan_item.queue.queue_position
|
|
77
78
|
self.check_alarms()
|
|
78
79
|
if self.scan_item.status == "closed":
|
|
79
80
|
break
|
|
80
81
|
if queue_pos is None:
|
|
81
|
-
logger.
|
|
82
|
+
logger.trace(f"Could not find queue entry for scan_id {self.scan_item.scan_id}")
|
|
82
83
|
continue
|
|
83
84
|
if queue_pos == 0:
|
|
84
85
|
break
|
|
@@ -160,7 +161,20 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
160
161
|
return header
|
|
161
162
|
|
|
162
163
|
def update_scan_item(self, timeout: float = 15):
|
|
163
|
-
"""
|
|
164
|
+
"""
|
|
165
|
+
Get the current scan item and update self.scan_item
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
timeout (float): timeout in seconds
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
RuntimeError: if no scan queue request is available
|
|
172
|
+
TimeoutError: if no scan item is found before reaching the timeout
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
if not self.scan_queue_request:
|
|
176
|
+
raise RuntimeError("No scan queue request available.")
|
|
177
|
+
|
|
164
178
|
start = time.time()
|
|
165
179
|
while self.scan_queue_request.scan is None:
|
|
166
180
|
self.check_alarms()
|
|
@@ -171,25 +185,38 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
171
185
|
|
|
172
186
|
def core(self):
|
|
173
187
|
"""core function to run the live updates for the table"""
|
|
188
|
+
self._wait_for_report_instructions()
|
|
189
|
+
show_table = self.report_instruction[self.REPORT_TYPE].get("show_table", True)
|
|
190
|
+
self._print_table_data = show_table
|
|
191
|
+
self._run_update(self.report_instruction[self.REPORT_TYPE]["points"])
|
|
192
|
+
|
|
193
|
+
def _wait_for_report_instructions(self):
|
|
194
|
+
"""wait until the report instructions are available"""
|
|
195
|
+
if not self.scan_queue_request or not self.scan_item or not self.scan_item.queue:
|
|
196
|
+
logger.warning(
|
|
197
|
+
f"Cannot wait for report instructions. scan_queue_request: {self.scan_queue_request}, scan_item: {self.scan_item}, scan_item.queue: {getattr(self.scan_item, 'queue', None)}"
|
|
198
|
+
)
|
|
199
|
+
return
|
|
174
200
|
req_ID = self.scan_queue_request.requestID
|
|
175
201
|
while True:
|
|
176
202
|
request_block = [
|
|
177
|
-
req for req in self.scan_item.queue.request_blocks if req
|
|
203
|
+
req for req in self.scan_item.queue.request_blocks if req.RID == req_ID
|
|
178
204
|
][0]
|
|
179
|
-
if not request_block
|
|
205
|
+
if not request_block.is_scan:
|
|
180
206
|
break
|
|
181
|
-
if request_block
|
|
207
|
+
if request_block.report_instructions:
|
|
182
208
|
break
|
|
183
209
|
self.check_alarms()
|
|
184
210
|
|
|
185
|
-
self._run_update(self.report_instruction[self.REPORT_TYPE])
|
|
186
|
-
|
|
187
211
|
def _run_update(self, target_num_points: int):
|
|
188
212
|
"""run the update loop with the progress bar
|
|
189
213
|
|
|
190
214
|
Args:
|
|
191
215
|
target_num_points (int): number of points to be collected
|
|
192
216
|
"""
|
|
217
|
+
if not self.scan_item:
|
|
218
|
+
logger.warning("No scan item available for live updates.")
|
|
219
|
+
return
|
|
193
220
|
with ScanProgressBar(
|
|
194
221
|
scan_number=self.scan_item.scan_number, clear_on_exit=self._print_table_data
|
|
195
222
|
) as progressbar:
|
|
@@ -211,7 +238,7 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
211
238
|
self.bec.callbacks.poll()
|
|
212
239
|
self.scan_item.poll_callbacks()
|
|
213
240
|
else:
|
|
214
|
-
logger.
|
|
241
|
+
logger.trace("waiting for new data point")
|
|
215
242
|
time.sleep(0.1)
|
|
216
243
|
|
|
217
244
|
if not self.scan_item.num_points:
|
|
@@ -1,31 +1,110 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import threading
|
|
3
4
|
from collections.abc import Callable
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING, cast
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
7
8
|
|
|
8
9
|
from bec_ipython_client.progressbar import DeviceProgressBar
|
|
10
|
+
from bec_lib import messages
|
|
9
11
|
from bec_lib.endpoints import MessageEndpoints
|
|
12
|
+
from bec_lib.redis_connector import MessageObject
|
|
10
13
|
|
|
11
14
|
from .utils import LiveUpdatesBase, check_alarms
|
|
12
15
|
|
|
13
16
|
if TYPE_CHECKING:
|
|
14
|
-
from bec_lib import messages
|
|
15
17
|
from bec_lib.client import BECClient
|
|
16
18
|
from bec_lib.devicemanager import DeviceManagerBase
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
class
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
class ReadbackDataHandler:
|
|
22
|
+
"""Helper class to get the current device values and request-done messages."""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self, device_manager: DeviceManagerBase, devices: list[str], request_id: str
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Helper class to get the current device values and request-done messages.
|
|
22
28
|
|
|
23
29
|
Args:
|
|
24
30
|
device_manager (DeviceManagerBase): device manager
|
|
25
31
|
devices (list): list of devices to monitor
|
|
32
|
+
request_id (str): request ID
|
|
26
33
|
"""
|
|
27
34
|
self.device_manager = device_manager
|
|
28
35
|
self.devices = devices
|
|
36
|
+
self.connector = device_manager.connector
|
|
37
|
+
self.request_id = request_id
|
|
38
|
+
self._devices_received = {dev: False for dev in devices}
|
|
39
|
+
self.data: dict[str, messages.DeviceMessage] = {}
|
|
40
|
+
self._devices_done_state: dict[str, tuple[bool, bool]] = {
|
|
41
|
+
dev: (False, False) for dev in devices
|
|
42
|
+
}
|
|
43
|
+
self.requests_done = threading.Event()
|
|
44
|
+
self._register_callbacks()
|
|
45
|
+
|
|
46
|
+
def _register_callbacks(self):
|
|
47
|
+
"""register callbacks for device readback messages."""
|
|
48
|
+
for dev in self.devices:
|
|
49
|
+
self.connector.register(
|
|
50
|
+
MessageEndpoints.device_readback(dev), cb=self.on_readback, parent=self, device=dev
|
|
51
|
+
)
|
|
52
|
+
self.connector.register(
|
|
53
|
+
MessageEndpoints.device_req_status(self.request_id),
|
|
54
|
+
cb=self.on_req_status,
|
|
55
|
+
from_start=True,
|
|
56
|
+
parent=self,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def _unregister_callbacks(self):
|
|
60
|
+
"""unregister callbacks for device readback messages."""
|
|
61
|
+
for dev in self.devices:
|
|
62
|
+
self.connector.unregister(MessageEndpoints.device_readback(dev), cb=self.on_readback)
|
|
63
|
+
self.connector.unregister(
|
|
64
|
+
MessageEndpoints.device_req_status(self.request_id), cb=self.on_req_status
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def on_req_status(
|
|
69
|
+
msg_obj: dict[str, messages.DeviceReqStatusMessage], parent: ReadbackDataHandler
|
|
70
|
+
):
|
|
71
|
+
"""Callback for device request status messages to track which devices are done.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
msg_obj (dict[str, messages.DeviceReqStatusMessage]): message object or device request status message
|
|
75
|
+
parent (ReadbackDataHandler): parent instance
|
|
76
|
+
"""
|
|
77
|
+
# pylint: disable=protected-access
|
|
78
|
+
msg = msg_obj["data"]
|
|
79
|
+
if msg.request_id != parent.request_id:
|
|
80
|
+
return
|
|
81
|
+
device = msg.device
|
|
82
|
+
parent._devices_done_state[device] = (True, msg.success)
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
all(done for done, _ in parent._devices_done_state.values())
|
|
86
|
+
and not parent.requests_done.is_set()
|
|
87
|
+
):
|
|
88
|
+
parent._on_request_done()
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def on_readback(msg_obj: MessageObject, parent: ReadbackDataHandler, device: str):
|
|
92
|
+
"""Callback for updating device readback data.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
msg_obj (MessageObject): message object
|
|
96
|
+
parent (ReadbackDataHandler): parent instance
|
|
97
|
+
device (str): device name
|
|
98
|
+
"""
|
|
99
|
+
# pylint: disable=protected-access
|
|
100
|
+
msg: messages.DeviceMessage = cast(messages.DeviceMessage, msg_obj.value)
|
|
101
|
+
parent._devices_received[device] = True
|
|
102
|
+
parent.data[device] = msg
|
|
103
|
+
|
|
104
|
+
def _on_request_done(self):
|
|
105
|
+
"""Callback for when all requests are done."""
|
|
106
|
+
self.requests_done.set()
|
|
107
|
+
self._unregister_callbacks()
|
|
29
108
|
|
|
30
109
|
def get_device_values(self) -> list:
|
|
31
110
|
"""get the current device values
|
|
@@ -35,57 +114,55 @@ class ReadbackDataMixin:
|
|
|
35
114
|
"""
|
|
36
115
|
values = []
|
|
37
116
|
for dev in self.devices:
|
|
38
|
-
val = self.
|
|
39
|
-
if
|
|
40
|
-
|
|
41
|
-
|
|
117
|
+
val = self.data.get(dev)
|
|
118
|
+
if val is None:
|
|
119
|
+
signal_data = self.device_manager.devices[dev].read(cached=True)
|
|
120
|
+
else:
|
|
121
|
+
signal_data = val.signals
|
|
42
122
|
# pylint: disable=protected-access
|
|
43
123
|
hints = self.device_manager.devices[dev]._hints
|
|
44
124
|
# if we have hints, use them to get the value, otherwise just use the first value
|
|
45
125
|
if hints:
|
|
46
|
-
values.append(
|
|
126
|
+
values.append(signal_data.get(hints[0]).get("value"))
|
|
47
127
|
else:
|
|
48
|
-
values.append(
|
|
128
|
+
values.append(signal_data.get(list(signal_data.keys())[0]).get("value"))
|
|
49
129
|
return values
|
|
50
130
|
|
|
51
|
-
def
|
|
52
|
-
"""
|
|
53
|
-
pipe = self.device_manager.connector.pipeline()
|
|
54
|
-
for dev in self.devices:
|
|
55
|
-
self.device_manager.connector.get(MessageEndpoints.device_req_status(dev), pipe)
|
|
56
|
-
return self.device_manager.connector.execute_pipeline(pipe)
|
|
131
|
+
def done(self) -> bool:
|
|
132
|
+
"""check if all devices are done
|
|
57
133
|
|
|
58
|
-
|
|
59
|
-
|
|
134
|
+
Returns:
|
|
135
|
+
bool: True if all devices are done, False otherwise
|
|
136
|
+
"""
|
|
137
|
+
return self.requests_done.is_set()
|
|
60
138
|
|
|
61
|
-
|
|
62
|
-
request (messages.ScanQueueMessage): request message
|
|
139
|
+
def device_states(self) -> dict[str, tuple[bool, bool]]:
|
|
63
140
|
"""
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
break
|
|
71
|
-
check_alarms(self.device_manager.parent)
|
|
141
|
+
Return the current device done states.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
dict: dictionary with device names as keys and tuples of (done, success) as values
|
|
145
|
+
"""
|
|
146
|
+
return self._devices_done_state
|
|
72
147
|
|
|
73
148
|
|
|
74
149
|
class LiveUpdatesReadbackProgressbar(LiveUpdatesBase):
|
|
75
150
|
"""Live feedback on motor movements using a progressbar.
|
|
76
151
|
|
|
77
152
|
Args:
|
|
78
|
-
|
|
79
|
-
|
|
153
|
+
bec (BECClient): BECClient instance
|
|
154
|
+
report_instruction (list, optional): report instruction for the scan. Defaults to None.
|
|
155
|
+
request (messages.ScanQueueMessage, optional): scan queue request message. Defaults to None.
|
|
156
|
+
callbacks (list[Callable], optional): list of callbacks to register. Defaults to None.
|
|
80
157
|
|
|
81
158
|
"""
|
|
82
159
|
|
|
83
160
|
def __init__(
|
|
84
161
|
self,
|
|
85
162
|
bec: BECClient,
|
|
86
|
-
report_instruction: list = None,
|
|
87
|
-
request: messages.ScanQueueMessage = None,
|
|
88
|
-
callbacks: list[Callable] = None,
|
|
163
|
+
report_instruction: list | None = None,
|
|
164
|
+
request: messages.ScanQueueMessage | None = None,
|
|
165
|
+
callbacks: list[Callable] | None = None,
|
|
89
166
|
) -> None:
|
|
90
167
|
super().__init__(
|
|
91
168
|
bec, report_instruction=report_instruction, request=request, callbacks=callbacks
|
|
@@ -97,18 +174,20 @@ class LiveUpdatesReadbackProgressbar(LiveUpdatesBase):
|
|
|
97
174
|
|
|
98
175
|
def core(self):
|
|
99
176
|
"""core function to monitor the device values and update the progressbar accordingly."""
|
|
100
|
-
|
|
177
|
+
request_id = self.request.metadata["RID"]
|
|
178
|
+
if self.report_instruction:
|
|
179
|
+
self.devices = self.report_instruction["readback"]["devices"]
|
|
180
|
+
request_id = self.report_instruction["readback"]["RID"]
|
|
181
|
+
data_source = ReadbackDataHandler(self.bec.device_manager, self.devices, request_id)
|
|
101
182
|
start_values = data_source.get_device_values()
|
|
102
183
|
self.wait_for_request_acceptance()
|
|
103
|
-
|
|
184
|
+
|
|
104
185
|
if self.report_instruction:
|
|
105
|
-
self.devices = self.report_instruction["readback"]["devices"]
|
|
106
186
|
target_values = self.report_instruction["readback"]["end"]
|
|
107
187
|
|
|
108
188
|
start_instr = self.report_instruction["readback"].get("start")
|
|
109
189
|
if start_instr:
|
|
110
190
|
start_values = self.report_instruction["readback"]["start"]
|
|
111
|
-
data_source = ReadbackDataMixin(self.bec.device_manager, self.devices)
|
|
112
191
|
else:
|
|
113
192
|
target_values = [
|
|
114
193
|
x for xs in self.request.content["parameter"]["args"].values() for x in xs
|
|
@@ -119,33 +198,16 @@ class LiveUpdatesReadbackProgressbar(LiveUpdatesBase):
|
|
|
119
198
|
with DeviceProgressBar(
|
|
120
199
|
self.devices, start_values=start_values, target_values=target_values
|
|
121
200
|
) as progress:
|
|
122
|
-
|
|
123
|
-
while not progress.finished or not
|
|
201
|
+
|
|
202
|
+
while not progress.finished or not data_source.done():
|
|
124
203
|
check_alarms(self.bec)
|
|
125
204
|
|
|
126
205
|
values = data_source.get_device_values()
|
|
127
206
|
progress.update(values=values)
|
|
128
207
|
self._print_client_msgs_asap()
|
|
129
208
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
msg.metadata["RID"] if (msg and msg.metadata.get("RID")) else None
|
|
133
|
-
for msg in msgs
|
|
134
|
-
]
|
|
135
|
-
|
|
136
|
-
if self.report_instruction:
|
|
137
|
-
compare_rids = set([self.report_instruction["readback"]["RID"]])
|
|
138
|
-
else:
|
|
139
|
-
compare_rids = set([self.request.metadata["RID"]])
|
|
140
|
-
if set(request_ids) != set(compare_rids):
|
|
141
|
-
progress.sleep()
|
|
142
|
-
continue
|
|
143
|
-
|
|
144
|
-
req_done = True
|
|
145
|
-
for dev, msg in zip(self.devices, msgs):
|
|
146
|
-
if not msg:
|
|
147
|
-
continue
|
|
148
|
-
if msg.content.get("success", False):
|
|
209
|
+
for dev, (done, success) in data_source.device_states().items():
|
|
210
|
+
if done and success:
|
|
149
211
|
progress.set_finished(dev)
|
|
150
212
|
# pylint: disable=protected-access
|
|
151
213
|
progress._progress.refresh()
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
|
-
import threading
|
|
5
4
|
import time
|
|
6
5
|
import traceback
|
|
7
6
|
from collections.abc import Callable
|
|
@@ -21,23 +20,6 @@ class ScanRequestError(Exception):
|
|
|
21
20
|
"""Error raised when a scan request is rejected"""
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
def set_event_delayed(event: threading.Event, delay: int) -> None:
|
|
25
|
-
"""Set event with a delay
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
event (threading.Event): event that should be set
|
|
29
|
-
delay (int): delay time in seconds
|
|
30
|
-
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
def call_set():
|
|
34
|
-
time.sleep(delay)
|
|
35
|
-
event.set()
|
|
36
|
-
|
|
37
|
-
thread = threading.Thread(target=call_set, daemon=True)
|
|
38
|
-
thread.start()
|
|
39
|
-
|
|
40
|
-
|
|
41
23
|
def check_alarms(bec):
|
|
42
24
|
"""check for alarms and raise them if needed"""
|
|
43
25
|
bec.alarm_handler.raise_alarms()
|
|
@@ -62,7 +44,7 @@ class LiveUpdatesBase(abc.ABC):
|
|
|
62
44
|
self.bec = bec
|
|
63
45
|
self.request = request
|
|
64
46
|
self.RID = request.metadata["RID"]
|
|
65
|
-
self.scan_queue_request = None
|
|
47
|
+
self.scan_queue_request: RequestItem | None = None
|
|
66
48
|
self.report_instruction = report_instruction
|
|
67
49
|
if callbacks is None:
|
|
68
50
|
self.callbacks = []
|
|
@@ -138,22 +120,22 @@ class ScanRequestMixin:
|
|
|
138
120
|
Returns:
|
|
139
121
|
RequestItem: scan queue request
|
|
140
122
|
"""
|
|
141
|
-
logger.
|
|
123
|
+
logger.trace("Waiting for request ID")
|
|
142
124
|
start = time.time()
|
|
143
125
|
while self.request_storage.find_request_by_ID(self.RID) is None:
|
|
144
126
|
time.sleep(0.1)
|
|
145
127
|
check_alarms(self.bec)
|
|
146
|
-
logger.
|
|
128
|
+
logger.trace(f"Waiting for request ID finished after {time.time()-start} s.")
|
|
147
129
|
return self.request_storage.find_request_by_ID(self.RID)
|
|
148
130
|
|
|
149
131
|
def _wait_for_scan_request_decision(self):
|
|
150
132
|
"""wait for a scan queuest decision"""
|
|
151
|
-
logger.
|
|
133
|
+
logger.trace("Waiting for decision")
|
|
152
134
|
start = time.time()
|
|
153
135
|
while self.scan_queue_request.decision_pending:
|
|
154
136
|
time.sleep(0.1)
|
|
155
137
|
check_alarms(self.bec)
|
|
156
|
-
logger.
|
|
138
|
+
logger.trace(f"Waiting for decision finished after {time.time()-start} s.")
|
|
157
139
|
|
|
158
140
|
def wait(self):
|
|
159
141
|
"""wait for the request acceptance"""
|