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.
Files changed (61) hide show
  1. {lima2_client-2.2.2/src/lima2_client.egg-info → lima2_client-3.0a0}/PKG-INFO +5 -3
  2. {lima2_client-2.2.2 → lima2_client-3.0a0}/pyproject.toml +4 -2
  3. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/draw_state_diagram.py +1 -2
  4. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/state_machine.py +7 -5
  5. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/client.py +17 -4
  6. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/detector.py +175 -29
  7. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/devencoded/dense_frame.py +1 -2
  8. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/devencoded/smx_sparse_frame.py +1 -2
  9. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/pipeline.py +98 -41
  10. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/pipelines/__init__.py +3 -3
  11. lima2_client-3.0a0/src/lima2/client/pipelines/failing.py +51 -0
  12. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/pipelines/legacy.py +2 -0
  13. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/pipelines/smx.py +4 -0
  14. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/pipelines/xpcs.py +3 -0
  15. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/progress_counter.py +20 -0
  16. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/shell.py +1 -1
  17. lima2_client-3.0a0/src/lima2/client/state_machine.py +251 -0
  18. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/topology.py +2 -2
  19. {lima2_client-2.2.2 → lima2_client-3.0a0/src/lima2_client.egg-info}/PKG-INFO +5 -3
  20. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2_client.egg-info/SOURCES.txt +2 -13
  21. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2_client.egg-info/requires.txt +3 -1
  22. lima2_client-2.2.2/src/lima2/client/state_machine.py +0 -331
  23. lima2_client-2.2.2/tests/test_client.py +0 -296
  24. lima2_client-2.2.2/tests/test_convert.py +0 -39
  25. lima2_client-2.2.2/tests/test_dense_frame.py +0 -155
  26. lima2_client-2.2.2/tests/test_detector.py +0 -142
  27. lima2_client-2.2.2/tests/test_pipeline.py +0 -688
  28. lima2_client-2.2.2/tests/test_pipelines.py +0 -20
  29. lima2_client-2.2.2/tests/test_progress_counter.py +0 -40
  30. lima2_client-2.2.2/tests/test_sparse_frame.py +0 -109
  31. lima2_client-2.2.2/tests/test_state_machine.py +0 -224
  32. lima2_client-2.2.2/tests/test_timer.py +0 -25
  33. lima2_client-2.2.2/tests/test_topology.py +0 -150
  34. lima2_client-2.2.2/tests/test_utils.py +0 -62
  35. {lima2_client-2.2.2 → lima2_client-3.0a0}/LICENSE +0 -0
  36. {lima2_client-2.2.2 → lima2_client-3.0a0}/README.md +0 -0
  37. {lima2_client-2.2.2 → lima2_client-3.0a0}/setup.cfg +0 -0
  38. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/__init__.py +0 -0
  39. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/examples/__init__.py +0 -0
  40. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/examples/async_github_pull_request.py +0 -0
  41. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/examples/async_turnstile.py +0 -0
  42. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/examples/boolean_field.py +0 -0
  43. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/examples/github_pull_request.py +0 -0
  44. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/examples/turnstile.py +0 -0
  45. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/finite_state_machine/exceptions.py +0 -0
  46. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/__init__.py +0 -0
  47. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/convert.py +0 -0
  48. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/devencoded/__init__.py +0 -0
  49. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/devencoded/exception.py +0 -0
  50. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/devencoded/sparse_frame.py +0 -0
  51. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/devencoded/structured_array.py +0 -0
  52. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/py.typed +0 -0
  53. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/client/utils.py +0 -0
  54. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/master_file/generate.py +0 -0
  55. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/server/__init__.py +0 -0
  56. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/server/control.py +0 -0
  57. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/server/receiver.py +0 -0
  58. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2/server/timer.py +0 -0
  59. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2_client.egg-info/dependency_links.txt +0 -0
  60. {lima2_client-2.2.2 → lima2_client-3.0a0}/src/lima2_client.egg-info/entry_points.txt +0 -0
  61. {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: 2.2.2
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.9
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<2.3
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.9"
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<2.3", # CI runner (CentOS 7) has glibc2.17, numpy>=2.3 needs glibc2.28+
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: List[TransitionDetails] = [
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, Union
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: Union[list, bool, int, str]
21
- target: Union[bool, int, str]
20
+ source: list | bool | int | str
21
+ target: bool | int | str
22
22
  conditions: list
23
- on_error: Union[bool, int, str]
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: Optional[UUID] = None) -> Pipeline:
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) and all(self.pipeline(UUID(uuid)).is_finished):
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.warning(
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: Optional[dict] = None
83
+ self.__params_schema: dict | None = None
82
84
  self.__fsm = fsm.StateMachine(self.__ctrl, self.__recvs)
83
- self.__try_sync_hard()
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
- return self.__fsm.state
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
- try:
337
- self.__fsm.sync()
338
- except Exception as e:
339
- _logger.warning(f"Unable to sync with server: {e}")
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
- return self.__fsm.state
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.__fsm.register_transition_logger(logger)
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.__fsm.unregister_transition_logger(logger)
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
- res = self.__fsm.prepare(str(uuid))
425
-
426
- return res.get() # Makes prepare_acq() synchronous
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
- self.__fsm.start()
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.__fsm.trigger()
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.__fsm.stop()
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.__fsm.reset_acq()
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 = Union[bytes, tuple[str, bytes]]
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: typing.Optional[int]
30
+ idx: int | None
32
31
  """Index of the frame where 0 is the first frame"""
33
32
 
34
33
  shape: tuple[int, ...]