lima2.client 2.2.2__tar.gz → 3.0a0__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.
- {lima2_client-2.2.2/src/lima2_client.egg-info → lima2_client-3.0a0}/PKG-INFO +5 -3
- {lima2_client-2.2.2 → lima2_client-3.0a0}/pyproject.toml +4 -2
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/draw_state_diagram.py +1 -2
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/state_machine.py +7 -5
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/client.py +17 -4
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/detector.py +175 -29
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/devencoded/dense_frame.py +1 -2
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/devencoded/smx_sparse_frame.py +1 -2
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/pipeline.py +98 -41
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/pipelines/__init__.py +3 -3
- lima2_client-3.0a0/src/lima2/client/pipelines/failing.py +51 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/pipelines/legacy.py +2 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/pipelines/smx.py +4 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/pipelines/xpcs.py +3 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/progress_counter.py +20 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/shell.py +1 -1
- lima2_client-3.0a0/src/lima2/client/state_machine.py +251 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/topology.py +2 -2
- {lima2_client-2.2.2 → lima2_client-3.0a0/src/lima2_client.egg-info}/PKG-INFO +5 -3
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2_client.egg-info/SOURCES.txt +2 -13
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2_client.egg-info/requires.txt +3 -1
- lima2_client-2.2.2/src/lima2/client/state_machine.py +0 -331
- lima2_client-2.2.2/tests/test_client.py +0 -296
- lima2_client-2.2.2/tests/test_convert.py +0 -39
- lima2_client-2.2.2/tests/test_dense_frame.py +0 -155
- lima2_client-2.2.2/tests/test_detector.py +0 -142
- lima2_client-2.2.2/tests/test_pipeline.py +0 -688
- lima2_client-2.2.2/tests/test_pipelines.py +0 -20
- lima2_client-2.2.2/tests/test_progress_counter.py +0 -40
- lima2_client-2.2.2/tests/test_sparse_frame.py +0 -109
- lima2_client-2.2.2/tests/test_state_machine.py +0 -224
- lima2_client-2.2.2/tests/test_timer.py +0 -25
- lima2_client-2.2.2/tests/test_topology.py +0 -150
- lima2_client-2.2.2/tests/test_utils.py +0 -62
- {lima2_client-2.2.2 → lima2_client-3.0a0}/LICENSE +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/README.md +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/setup.cfg +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/__init__.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/examples/__init__.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/examples/async_github_pull_request.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/examples/async_turnstile.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/examples/boolean_field.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/examples/github_pull_request.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/examples/turnstile.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/exceptions.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/__init__.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/convert.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/devencoded/__init__.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/devencoded/exception.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/devencoded/sparse_frame.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/devencoded/structured_array.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/py.typed +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/utils.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/master_file/generate.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/server/__init__.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/server/control.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/server/receiver.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/server/timer.py +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2_client.egg-info/dependency_links.txt +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2_client.egg-info/entry_points.txt +0 -0
- {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2_client.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lima2-client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0a0
|
|
4
4
|
Summary: Python client library for Lima2 detector
|
|
5
5
|
Author: BCU (ESRF)
|
|
6
6
|
License: Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
@@ -16,7 +16,7 @@ Project-URL: Documentation, https://limagroup.gitlab-pages.esrf.fr/lima2-client/
|
|
|
16
16
|
Classifier: Intended Audience :: Science/Research
|
|
17
17
|
Classifier: License :: OSI Approved :: MIT License
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Requires-Python: >=3.
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
22
|
Requires-Dist: pytango
|
|
@@ -24,7 +24,7 @@ Requires-Dist: gevent
|
|
|
24
24
|
Requires-Dist: jsonschema
|
|
25
25
|
Requires-Dist: jsonschema-default
|
|
26
26
|
Requires-Dist: beartype
|
|
27
|
-
Requires-Dist: numpy
|
|
27
|
+
Requires-Dist: numpy
|
|
28
28
|
Requires-Dist: pyyaml
|
|
29
29
|
Requires-Dist: typing-extensions
|
|
30
30
|
Requires-Dist: h5py
|
|
@@ -33,8 +33,10 @@ Requires-Dist: black==22.3; extra == "dev"
|
|
|
33
33
|
Requires-Dist: mypy; extra == "dev"
|
|
34
34
|
Requires-Dist: types-pyyaml; extra == "dev"
|
|
35
35
|
Requires-Dist: types-jsonschema; extra == "dev"
|
|
36
|
+
Requires-Dist: types-gevent; extra == "dev"
|
|
36
37
|
Requires-Dist: flake8; extra == "dev"
|
|
37
38
|
Requires-Dist: ipython; extra == "dev"
|
|
39
|
+
Requires-Dist: nosqltangodb; extra == "dev"
|
|
38
40
|
Requires-Dist: pytango-db; extra == "dev"
|
|
39
41
|
Requires-Dist: pytest; extra == "dev"
|
|
40
42
|
Requires-Dist: pytest-forked; extra == "dev"
|
|
@@ -7,7 +7,7 @@ version_scheme = "guess-next-dev"
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "lima2-client"
|
|
10
|
-
requires-python = ">= 3.
|
|
10
|
+
requires-python = ">= 3.10"
|
|
11
11
|
dynamic = ["version"]
|
|
12
12
|
authors = [{ name = "BCU (ESRF)" }]
|
|
13
13
|
description = "Python client library for Lima2 detector"
|
|
@@ -24,7 +24,7 @@ dependencies = [
|
|
|
24
24
|
"jsonschema",
|
|
25
25
|
"jsonschema-default",
|
|
26
26
|
"beartype",
|
|
27
|
-
"numpy
|
|
27
|
+
"numpy",
|
|
28
28
|
"pyyaml",
|
|
29
29
|
"typing-extensions",
|
|
30
30
|
"h5py",
|
|
@@ -43,8 +43,10 @@ dev = [
|
|
|
43
43
|
"mypy",
|
|
44
44
|
"types-pyyaml",
|
|
45
45
|
"types-jsonschema",
|
|
46
|
+
"types-gevent",
|
|
46
47
|
"flake8",
|
|
47
48
|
"ipython",
|
|
49
|
+
"nosqltangodb",
|
|
48
50
|
"pytango-db",
|
|
49
51
|
"pytest",
|
|
50
52
|
"pytest-forked",
|
|
@@ -3,7 +3,6 @@ import importlib
|
|
|
3
3
|
import inspect
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
|
-
from typing import List
|
|
7
6
|
|
|
8
7
|
from finite_state_machine.state_machine import TransitionDetails
|
|
9
8
|
|
|
@@ -28,7 +27,7 @@ def generate_state_diagram_markdown(cls, initial_state):
|
|
|
28
27
|
"""
|
|
29
28
|
|
|
30
29
|
class_fns = inspect.getmembers(cls, predicate=inspect.isfunction)
|
|
31
|
-
state_transitions:
|
|
30
|
+
state_transitions: list[TransitionDetails] = [
|
|
32
31
|
func._fsm for name, func in class_fns if hasattr(func, "_fsm")
|
|
33
32
|
]
|
|
34
33
|
|
|
@@ -2,7 +2,7 @@ import asyncio
|
|
|
2
2
|
from enum import Enum
|
|
3
3
|
import functools
|
|
4
4
|
import types
|
|
5
|
-
from typing import NamedTuple
|
|
5
|
+
from typing import NamedTuple
|
|
6
6
|
|
|
7
7
|
from .exceptions import ConditionsNotMet, InvalidStartState
|
|
8
8
|
|
|
@@ -17,10 +17,10 @@ class StateMachine:
|
|
|
17
17
|
|
|
18
18
|
class TransitionDetails(NamedTuple):
|
|
19
19
|
name: str
|
|
20
|
-
source:
|
|
21
|
-
target:
|
|
20
|
+
source: list | bool | int | str
|
|
21
|
+
target: bool | int | str
|
|
22
22
|
conditions: list
|
|
23
|
-
on_error:
|
|
23
|
+
on_error: bool | int | str
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class transition:
|
|
@@ -95,11 +95,13 @@ class transition:
|
|
|
95
95
|
result = func(*args, **kwargs)
|
|
96
96
|
state_machine.state = self.target
|
|
97
97
|
return result
|
|
98
|
-
except Exception:
|
|
98
|
+
except Exception as e:
|
|
99
99
|
# TODO should we log this somewhere?
|
|
100
100
|
# logger.error? maybe have an optional parameter to set this up
|
|
101
101
|
# how to libraries log?
|
|
102
102
|
state_machine.state = self.on_error
|
|
103
|
+
if self.reraise:
|
|
104
|
+
raise e
|
|
103
105
|
return
|
|
104
106
|
|
|
105
107
|
@functools.wraps(func)
|
|
@@ -20,12 +20,12 @@ import logging
|
|
|
20
20
|
import os
|
|
21
21
|
import traceback
|
|
22
22
|
from concurrent import futures
|
|
23
|
+
from typing import Any
|
|
23
24
|
from uuid import UUID
|
|
24
25
|
|
|
25
26
|
import tango as tg
|
|
26
27
|
import yaml
|
|
27
28
|
from beartype import beartype
|
|
28
|
-
from typing_extensions import Any, Optional
|
|
29
29
|
|
|
30
30
|
from lima2.client import pipelines, progress_counter, state_machine, topology
|
|
31
31
|
from lima2.client.detector import Detector
|
|
@@ -161,6 +161,19 @@ class Client:
|
|
|
161
161
|
|
|
162
162
|
self.detector.prepare_acq(uuid, ctl, acq, proc)
|
|
163
163
|
|
|
164
|
+
def on_error(evt: tg.DataReadyEventData):
|
|
165
|
+
logger.error(
|
|
166
|
+
f"Pipeline {evt.device.dev_name()} failed.\n"
|
|
167
|
+
f"Reason: '{evt.device.last_error}'"
|
|
168
|
+
)
|
|
169
|
+
if str(self.current_pipeline.uuid) in evt.device.dev_name():
|
|
170
|
+
logger.info(
|
|
171
|
+
f"Stopping current acquisition ({self.current_pipeline.uuid})..."
|
|
172
|
+
)
|
|
173
|
+
self.detector.abort_acq()
|
|
174
|
+
|
|
175
|
+
self.current_pipeline.register_on_error(on_error)
|
|
176
|
+
|
|
164
177
|
@docstring_from(Detector.start_acq)
|
|
165
178
|
def start_acq(self) -> None:
|
|
166
179
|
self.detector.start_acq()
|
|
@@ -218,7 +231,7 @@ class Client:
|
|
|
218
231
|
return []
|
|
219
232
|
return [UUID(u) for u in {y for x in uuids for y in x}]
|
|
220
233
|
|
|
221
|
-
def pipeline(self, uuid:
|
|
234
|
+
def pipeline(self, uuid: UUID | None = None) -> Pipeline:
|
|
222
235
|
"""Get a specific pipeline by uuid. Return the current one by default (uuid=None)"""
|
|
223
236
|
if uuid is None:
|
|
224
237
|
return self.current_pipeline
|
|
@@ -299,7 +312,7 @@ class Client:
|
|
|
299
312
|
|
|
300
313
|
cleared = []
|
|
301
314
|
for uuid in receiver.pipelines:
|
|
302
|
-
if uuid != str(current)
|
|
315
|
+
if uuid != str(current):
|
|
303
316
|
logger.debug(
|
|
304
317
|
f"Erasing pipeline {uuid} from recv {receiver.dev_name()}"
|
|
305
318
|
)
|
|
@@ -314,7 +327,7 @@ class Client:
|
|
|
314
327
|
try:
|
|
315
328
|
cleared = cleared.union(future.result())
|
|
316
329
|
except Exception:
|
|
317
|
-
logger.
|
|
330
|
+
logger.info(
|
|
318
331
|
f"Exception while clearing pipelines from {receiver.dev_name()}: "
|
|
319
332
|
f"{traceback.format_exc()}"
|
|
320
333
|
)
|
|
@@ -18,14 +18,16 @@ Typical usage example:
|
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
+
import contextlib
|
|
21
22
|
import json
|
|
22
23
|
import logging
|
|
23
24
|
import uuid
|
|
25
|
+
from collections.abc import Iterator
|
|
24
26
|
|
|
25
27
|
import tango
|
|
26
28
|
from beartype import beartype
|
|
29
|
+
from gevent.lock import BoundedSemaphore
|
|
27
30
|
from jsonschema_default import create_from
|
|
28
|
-
from typing_extensions import Optional
|
|
29
31
|
|
|
30
32
|
from lima2.client import pipelines
|
|
31
33
|
from lima2.client import state_machine as fsm
|
|
@@ -78,9 +80,12 @@ class Detector:
|
|
|
78
80
|
f"Receiver MPI rank order does not match client order: {rank_order}"
|
|
79
81
|
)
|
|
80
82
|
|
|
81
|
-
self.__params_schema:
|
|
83
|
+
self.__params_schema: dict | None = None
|
|
82
84
|
self.__fsm = fsm.StateMachine(self.__ctrl, self.__recvs)
|
|
83
|
-
self.
|
|
85
|
+
self.__fsm_lock = BoundedSemaphore()
|
|
86
|
+
|
|
87
|
+
self._register_state_change_handlers()
|
|
88
|
+
self.sync_hard()
|
|
84
89
|
|
|
85
90
|
if _logger.getEffectiveLevel() == logging.DEBUG:
|
|
86
91
|
# Log transition of states
|
|
@@ -93,6 +98,14 @@ class Detector:
|
|
|
93
98
|
d.set_green_mode(tango.GreenMode.Gevent)
|
|
94
99
|
d.set_timeout_millis(timeout * 1000)
|
|
95
100
|
|
|
101
|
+
@contextlib.contextmanager
|
|
102
|
+
def _locked_fsm(self) -> Iterator[fsm.StateMachine]:
|
|
103
|
+
self.__fsm_lock.acquire()
|
|
104
|
+
try:
|
|
105
|
+
yield self.__fsm
|
|
106
|
+
finally:
|
|
107
|
+
self.__fsm_lock.release()
|
|
108
|
+
|
|
96
109
|
@property
|
|
97
110
|
def det_info(self) -> dict:
|
|
98
111
|
"""
|
|
@@ -313,18 +326,8 @@ class Detector:
|
|
|
313
326
|
Group:
|
|
314
327
|
State
|
|
315
328
|
"""
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
def __try_sync_hard(self):
|
|
319
|
-
"""
|
|
320
|
-
Try to init the state from a lazy constructed device Proxy
|
|
321
|
-
(to be used when the DS is not running when Detector is constructed)
|
|
322
|
-
"""
|
|
323
|
-
try:
|
|
324
|
-
self.ping()
|
|
325
|
-
self.sync_hard()
|
|
326
|
-
except tango.DevFailed:
|
|
327
|
-
return False
|
|
329
|
+
with self._locked_fsm() as fsm:
|
|
330
|
+
return fsm.state
|
|
328
331
|
|
|
329
332
|
def sync_hard(self):
|
|
330
333
|
"""
|
|
@@ -333,12 +336,13 @@ class Detector:
|
|
|
333
336
|
Group:
|
|
334
337
|
State
|
|
335
338
|
"""
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
339
|
+
with self._locked_fsm() as fsm:
|
|
340
|
+
try:
|
|
341
|
+
fsm.sync()
|
|
342
|
+
except Exception as e:
|
|
343
|
+
_logger.warning(f"Unable to sync with server: {e}")
|
|
340
344
|
|
|
341
|
-
|
|
345
|
+
return fsm.state
|
|
342
346
|
|
|
343
347
|
@property
|
|
344
348
|
def dev_states(self):
|
|
@@ -366,7 +370,8 @@ class Detector:
|
|
|
366
370
|
Group:
|
|
367
371
|
State
|
|
368
372
|
"""
|
|
369
|
-
self.
|
|
373
|
+
with self._locked_fsm() as fsm:
|
|
374
|
+
fsm.register_transition_logger(logger)
|
|
370
375
|
|
|
371
376
|
def unregister_transition_logger(self, logger):
|
|
372
377
|
"""
|
|
@@ -378,7 +383,8 @@ class Detector:
|
|
|
378
383
|
Group:
|
|
379
384
|
State
|
|
380
385
|
"""
|
|
381
|
-
self.
|
|
386
|
+
with self._locked_fsm() as fsm:
|
|
387
|
+
fsm.unregister_transition_logger(logger)
|
|
382
388
|
|
|
383
389
|
@beartype
|
|
384
390
|
def prepare_acq(
|
|
@@ -421,9 +427,9 @@ class Detector:
|
|
|
421
427
|
recv.acq_params = json.dumps(acq_params[i])
|
|
422
428
|
recv.proc_params = json.dumps(proc_params[i])
|
|
423
429
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
430
|
+
# Raises and reverts to IDLE on exception
|
|
431
|
+
with self._locked_fsm() as fsm:
|
|
432
|
+
fsm.prepare(str(uuid))
|
|
427
433
|
|
|
428
434
|
def start_acq(self):
|
|
429
435
|
"""Start acquisition.
|
|
@@ -431,7 +437,11 @@ class Detector:
|
|
|
431
437
|
Group:
|
|
432
438
|
Control
|
|
433
439
|
"""
|
|
434
|
-
|
|
440
|
+
|
|
441
|
+
self._register_on_end_acq_handlers()
|
|
442
|
+
|
|
443
|
+
with self._locked_fsm() as fsm:
|
|
444
|
+
fsm.start()
|
|
435
445
|
|
|
436
446
|
def trigger(self):
|
|
437
447
|
"""Software trigger.
|
|
@@ -439,7 +449,11 @@ class Detector:
|
|
|
439
449
|
Group:
|
|
440
450
|
Control
|
|
441
451
|
"""
|
|
442
|
-
self.
|
|
452
|
+
with self._locked_fsm() as fsm:
|
|
453
|
+
if fsm.state == State.FAULT:
|
|
454
|
+
_logger.warning("Got trigger() while in FAULT state -> ignoring")
|
|
455
|
+
else:
|
|
456
|
+
fsm.trigger()
|
|
443
457
|
|
|
444
458
|
def stop_acq(self):
|
|
445
459
|
"""
|
|
@@ -448,7 +462,9 @@ class Detector:
|
|
|
448
462
|
Group:
|
|
449
463
|
Control
|
|
450
464
|
"""
|
|
451
|
-
self.
|
|
465
|
+
with self._locked_fsm() as fsm:
|
|
466
|
+
fsm.stop()
|
|
467
|
+
fsm.close()
|
|
452
468
|
|
|
453
469
|
def reset_acq(self):
|
|
454
470
|
"""
|
|
@@ -457,7 +473,18 @@ class Detector:
|
|
|
457
473
|
Group:
|
|
458
474
|
Control
|
|
459
475
|
"""
|
|
460
|
-
self.
|
|
476
|
+
with self._locked_fsm() as fsm:
|
|
477
|
+
fsm.reset_acq()
|
|
478
|
+
|
|
479
|
+
def abort_acq(self):
|
|
480
|
+
"""
|
|
481
|
+
Abort current acquisition (call Stop on running devices) and go to FAULT state.
|
|
482
|
+
|
|
483
|
+
Group:
|
|
484
|
+
Control
|
|
485
|
+
"""
|
|
486
|
+
with self._locked_fsm() as fsm:
|
|
487
|
+
fsm.abort()
|
|
461
488
|
|
|
462
489
|
@property
|
|
463
490
|
def ctrl_params(self):
|
|
@@ -532,3 +559,122 @@ class Detector:
|
|
|
532
559
|
# yield self.get_processing(self, uuid)
|
|
533
560
|
# finally:
|
|
534
561
|
# self.erase_processing(uuid)
|
|
562
|
+
|
|
563
|
+
def _register_state_change_handlers(self):
|
|
564
|
+
"""Register a "change of acq_state" callback on each device.
|
|
565
|
+
|
|
566
|
+
These handlers are responsible for handling receivers entering FAULT state
|
|
567
|
+
(e.g. when the pipeline throws an exception), and for setting the global state
|
|
568
|
+
to DISCONNECTED if we receive errors on the tango event channel.
|
|
569
|
+
"""
|
|
570
|
+
|
|
571
|
+
disconnected_devices = set()
|
|
572
|
+
|
|
573
|
+
def on_acq_state_change(evt: tango.EventData):
|
|
574
|
+
"""Handler for state changes of each individual device"""
|
|
575
|
+
nonlocal disconnected_devices
|
|
576
|
+
|
|
577
|
+
# Handle disconnect
|
|
578
|
+
if evt.err:
|
|
579
|
+
if evt.device not in disconnected_devices:
|
|
580
|
+
_logger.error(
|
|
581
|
+
f"Lost connection to device {evt.device.dev_name()}:\n"
|
|
582
|
+
+ "\n".join([f"- {err.desc}" for err in evt.errors])
|
|
583
|
+
+ "\n"
|
|
584
|
+
)
|
|
585
|
+
disconnected_devices.add(evt.device)
|
|
586
|
+
with self._locked_fsm() as fsm:
|
|
587
|
+
fsm.disconnect()
|
|
588
|
+
return
|
|
589
|
+
|
|
590
|
+
# Handle FAULT
|
|
591
|
+
AcqState = type(evt.device.acq_state)
|
|
592
|
+
acq_state = AcqState(evt.attr_value.value)
|
|
593
|
+
_logger.debug(f"{evt.device.dev_name()} changed state to {acq_state.name}")
|
|
594
|
+
|
|
595
|
+
if acq_state == AcqState.fault:
|
|
596
|
+
_logger.error(
|
|
597
|
+
f"Device {evt.device.dev_name()} in FAULT state.\n"
|
|
598
|
+
f"Reason: '{evt.device.last_error}'\n"
|
|
599
|
+
)
|
|
600
|
+
if self.state not in [State.FAULT, State.UNKNOWN]:
|
|
601
|
+
_logger.error("Aborting acquisition.")
|
|
602
|
+
with self._locked_fsm() as fsm:
|
|
603
|
+
fsm.abort()
|
|
604
|
+
|
|
605
|
+
# Handle reconnect
|
|
606
|
+
if evt.device in disconnected_devices:
|
|
607
|
+
_logger.info(f"{evt.device.dev_name()} seems back up")
|
|
608
|
+
disconnected_devices.remove(evt.device)
|
|
609
|
+
|
|
610
|
+
if self.state == State.DISCONNECTED:
|
|
611
|
+
_logger.info("Trying to sync...")
|
|
612
|
+
with self._locked_fsm() as fsm:
|
|
613
|
+
try:
|
|
614
|
+
fsm.sync()
|
|
615
|
+
disconnected_devices = set()
|
|
616
|
+
print("Lima2 reconnected.")
|
|
617
|
+
except Exception as e:
|
|
618
|
+
_logger.warning(f"Sync failed: {e}")
|
|
619
|
+
finally:
|
|
620
|
+
_logger.info(f"Current state: {fsm.state}")
|
|
621
|
+
|
|
622
|
+
for dev in self.devs:
|
|
623
|
+
dev.subscribe_event(
|
|
624
|
+
"acq_state",
|
|
625
|
+
tango.EventType.CHANGE_EVENT,
|
|
626
|
+
on_acq_state_change,
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
def _register_on_end_acq_handlers(self):
|
|
630
|
+
"""Register an "end of acquisition" callback on each receiver.
|
|
631
|
+
|
|
632
|
+
This handler callback is responsible for calling Close on the control device when
|
|
633
|
+
all receivers are finished transferring frames.
|
|
634
|
+
"""
|
|
635
|
+
nb_frames_xferreds = []
|
|
636
|
+
event_ids = {}
|
|
637
|
+
|
|
638
|
+
def on_end_acq(evt):
|
|
639
|
+
nonlocal nb_frames_xferreds
|
|
640
|
+
|
|
641
|
+
# Filter spurious error events (if the device is restarted)
|
|
642
|
+
if evt.err:
|
|
643
|
+
_logger.error(
|
|
644
|
+
f"Error on {evt.device.dev_name()} 'nb_frames_xferred' event:\n"
|
|
645
|
+
+ "\n".join([f"- {err.desc}" for err in evt.errors])
|
|
646
|
+
+ "\n"
|
|
647
|
+
)
|
|
648
|
+
for r, e in event_ids.items():
|
|
649
|
+
r.unsubscribe_event(e)
|
|
650
|
+
return
|
|
651
|
+
|
|
652
|
+
nb_frames_xferreds.append(evt)
|
|
653
|
+
|
|
654
|
+
if len(nb_frames_xferreds) == len(self.recvs):
|
|
655
|
+
for r, e in event_ids.items():
|
|
656
|
+
r.unsubscribe_event(e)
|
|
657
|
+
|
|
658
|
+
_logger.debug(f"on_end_acq while {self.state}")
|
|
659
|
+
with self._locked_fsm() as fsm:
|
|
660
|
+
if fsm.state == State.RUNNING:
|
|
661
|
+
fsm.close()
|
|
662
|
+
elif fsm.state == State.FAULT:
|
|
663
|
+
# Acquisition aborted -> do nothing
|
|
664
|
+
pass
|
|
665
|
+
else:
|
|
666
|
+
# Unexpected
|
|
667
|
+
_logger.info(f"Acquisition ended while in state {fsm.state}")
|
|
668
|
+
|
|
669
|
+
nb_frames_xferreds = []
|
|
670
|
+
|
|
671
|
+
# On server side, data_ready_event is pushed by each receiver
|
|
672
|
+
# at the end of the acquisition loop.
|
|
673
|
+
event_ids = {
|
|
674
|
+
r: r.subscribe_event(
|
|
675
|
+
"nb_frames_xferred",
|
|
676
|
+
tango.EventType.DATA_READY_EVENT,
|
|
677
|
+
on_end_acq,
|
|
678
|
+
)
|
|
679
|
+
for r in self.recvs
|
|
680
|
+
}
|
|
@@ -11,7 +11,6 @@ import struct
|
|
|
11
11
|
from dataclasses import dataclass
|
|
12
12
|
|
|
13
13
|
import numpy
|
|
14
|
-
from typing_extensions import Union
|
|
15
14
|
|
|
16
15
|
from .exception import DevEncodedFormatNotSupported
|
|
17
16
|
|
|
@@ -19,7 +18,7 @@ DATA_HEADER_FORMAT = "<IHHIIHHHHHHHHIIIIIIQ"
|
|
|
19
18
|
DATA_MAGIC = struct.unpack(">I", b"DTAY")[0]
|
|
20
19
|
DATA_HEADER_SIZE = struct.calcsize(DATA_HEADER_FORMAT)
|
|
21
20
|
|
|
22
|
-
EncodedFrame =
|
|
21
|
+
EncodedFrame = bytes | tuple[str, bytes]
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
class IMAGE_MODES(enum.Enum):
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
import struct
|
|
10
10
|
import numpy as np
|
|
11
|
-
import typing
|
|
12
11
|
from dataclasses import dataclass
|
|
13
12
|
|
|
14
13
|
from .exception import DevEncodedFormatNotSupported
|
|
@@ -28,7 +27,7 @@ class SmxSparseFrame:
|
|
|
28
27
|
background_avg: np.ndarray
|
|
29
28
|
background_std: np.ndarray
|
|
30
29
|
|
|
31
|
-
idx:
|
|
30
|
+
idx: int | None
|
|
32
31
|
"""Index of the frame where 0 is the first frame"""
|
|
33
32
|
|
|
34
33
|
shape: tuple[int, ...]
|