ophyd-async 0.1.0__py3-none-any.whl → 0.3a1__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.
Files changed (60) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +47 -12
  3. ophyd_async/core/_providers.py +66 -0
  4. ophyd_async/core/async_status.py +7 -5
  5. ophyd_async/core/detector.py +321 -0
  6. ophyd_async/core/device.py +184 -0
  7. ophyd_async/core/device_save_loader.py +286 -0
  8. ophyd_async/core/flyer.py +94 -0
  9. ophyd_async/core/{_device/_signal/signal.py → signal.py} +46 -18
  10. ophyd_async/core/{_device/_backend/signal_backend.py → signal_backend.py} +6 -2
  11. ophyd_async/core/{_device/_backend/sim_signal_backend.py → sim_signal_backend.py} +6 -2
  12. ophyd_async/core/{_device/standard_readable.py → standard_readable.py} +3 -3
  13. ophyd_async/core/utils.py +79 -29
  14. ophyd_async/epics/_backend/_aioca.py +38 -25
  15. ophyd_async/epics/_backend/_p4p.py +62 -27
  16. ophyd_async/epics/_backend/common.py +20 -0
  17. ophyd_async/epics/areadetector/__init__.py +10 -13
  18. ophyd_async/epics/areadetector/controllers/__init__.py +4 -0
  19. ophyd_async/epics/areadetector/controllers/ad_sim_controller.py +52 -0
  20. ophyd_async/epics/areadetector/controllers/pilatus_controller.py +49 -0
  21. ophyd_async/epics/areadetector/drivers/__init__.py +15 -0
  22. ophyd_async/epics/areadetector/drivers/ad_base.py +111 -0
  23. ophyd_async/epics/areadetector/drivers/pilatus_driver.py +18 -0
  24. ophyd_async/epics/areadetector/single_trigger_det.py +4 -4
  25. ophyd_async/epics/areadetector/utils.py +91 -3
  26. ophyd_async/epics/areadetector/writers/__init__.py +5 -0
  27. ophyd_async/epics/areadetector/writers/_hdfdataset.py +10 -0
  28. ophyd_async/epics/areadetector/writers/_hdffile.py +54 -0
  29. ophyd_async/epics/areadetector/writers/hdf_writer.py +133 -0
  30. ophyd_async/epics/areadetector/{nd_file_hdf.py → writers/nd_file_hdf.py} +22 -5
  31. ophyd_async/epics/areadetector/writers/nd_plugin.py +30 -0
  32. ophyd_async/epics/demo/__init__.py +3 -2
  33. ophyd_async/epics/demo/demo_ad_sim_detector.py +35 -0
  34. ophyd_async/epics/motion/motor.py +2 -1
  35. ophyd_async/epics/pvi.py +70 -0
  36. ophyd_async/epics/signal/__init__.py +0 -2
  37. ophyd_async/epics/signal/signal.py +1 -1
  38. ophyd_async/panda/__init__.py +12 -8
  39. ophyd_async/panda/panda.py +43 -134
  40. ophyd_async/panda/panda_controller.py +41 -0
  41. ophyd_async/panda/table.py +158 -0
  42. ophyd_async/panda/utils.py +15 -0
  43. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/METADATA +49 -42
  44. ophyd_async-0.3a1.dist-info/RECORD +56 -0
  45. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/WHEEL +1 -1
  46. ophyd_async/core/_device/__init__.py +0 -0
  47. ophyd_async/core/_device/_backend/__init__.py +0 -0
  48. ophyd_async/core/_device/_signal/__init__.py +0 -0
  49. ophyd_async/core/_device/device.py +0 -60
  50. ophyd_async/core/_device/device_collector.py +0 -121
  51. ophyd_async/core/_device/device_vector.py +0 -14
  52. ophyd_async/epics/areadetector/ad_driver.py +0 -18
  53. ophyd_async/epics/areadetector/directory_provider.py +0 -18
  54. ophyd_async/epics/areadetector/hdf_streamer_det.py +0 -167
  55. ophyd_async/epics/areadetector/nd_plugin.py +0 -13
  56. ophyd_async/epics/signal/pvi_get.py +0 -22
  57. ophyd_async-0.1.0.dist-info/RECORD +0 -45
  58. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/LICENSE +0 -0
  59. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/entry_points.txt +0 -0
  60. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ophyd-async
3
- Version: 0.1.0
3
+ Version: 0.3a1
4
4
  Summary: Asynchronous Bluesky hardware abstraction code, compatible with control systems like EPICS and Tango
5
5
  Author-email: Tom Cobb <tom.cobb@diamond.ac.uk>
6
6
  License: BSD 3-Clause License
@@ -45,23 +45,17 @@ Requires-Dist: networkx >=2.0
45
45
  Requires-Dist: numpy
46
46
  Requires-Dist: packaging
47
47
  Requires-Dist: pint
48
- Requires-Dist: bluesky
48
+ Requires-Dist: bluesky >=1.13.0a3
49
49
  Requires-Dist: event-model
50
50
  Requires-Dist: p4p
51
+ Requires-Dist: pyyaml
51
52
  Requires-Dist: typing-extensions ; python_version < "3.8"
52
53
  Provides-Extra: ca
53
54
  Requires-Dist: aioca >=1.6 ; extra == 'ca'
54
55
  Provides-Extra: dev
55
56
  Requires-Dist: ophyd-async[pva] ; extra == 'dev'
56
57
  Requires-Dist: ophyd-async[ca] ; extra == 'dev'
57
- Requires-Dist: attrs >=19.3.0 ; extra == 'dev'
58
58
  Requires-Dist: black ; extra == 'dev'
59
- Requires-Dist: bluesky >=1.11.0 ; extra == 'dev'
60
- Requires-Dist: caproto[standard] >=0.4.2rc1 ; extra == 'dev'
61
- Requires-Dist: pytest-codecov ; extra == 'dev'
62
- Requires-Dist: databroker >=1.0.0b1 ; extra == 'dev'
63
- Requires-Dist: doctr ; extra == 'dev'
64
- Requires-Dist: epics-pypdb ; extra == 'dev'
65
59
  Requires-Dist: flake8 ; extra == 'dev'
66
60
  Requires-Dist: flake8-isort ; extra == 'dev'
67
61
  Requires-Dist: Flake8-pyproject ; extra == 'dev'
@@ -74,6 +68,7 @@ Requires-Dist: mypy ; extra == 'dev'
74
68
  Requires-Dist: myst-parser ; extra == 'dev'
75
69
  Requires-Dist: numpydoc ; extra == 'dev'
76
70
  Requires-Dist: ophyd ; extra == 'dev'
71
+ Requires-Dist: pickleshare ; extra == 'dev'
77
72
  Requires-Dist: pipdeptree ; extra == 'dev'
78
73
  Requires-Dist: pre-commit ; extra == 'dev'
79
74
  Requires-Dist: pydata-sphinx-theme >=0.12 ; extra == 'dev'
@@ -90,55 +85,61 @@ Requires-Dist: sphinx-copybutton ; extra == 'dev'
90
85
  Requires-Dist: sphinx-design ; extra == 'dev'
91
86
  Requires-Dist: tox-direct ; extra == 'dev'
92
87
  Requires-Dist: types-mock ; extra == 'dev'
88
+ Requires-Dist: types-pyyaml ; extra == 'dev'
93
89
  Provides-Extra: pva
94
90
  Requires-Dist: p4p ; extra == 'pva'
95
91
 
96
- ***********
97
92
  Ophyd Async
98
- ***********
93
+ ===========
99
94
 
100
- |build_status| |coverage| |pypi_version| |license|
95
+ |code_ci| |docs_ci| |coverage| |pypi_version| |license|
101
96
 
102
- Ophyd is a Python library for interfacing with hardware. It provides an
103
- abstraction layer that enables experiment orchestration and data acquisition
104
- code to operate above the specifics of particular devices and control systems.
97
+ Asynchronous device abstraction framework, building on `Ophyd`_.
105
98
 
106
- Ophyd is typically used with the `Bluesky Run Engine`_ for experiment
107
- orchestration and data acquisition. It is also sometimes used in a stand-alone
108
- fashion.
99
+ ============== ==============================================================
100
+ PyPI ``pip install ophyd-async``
101
+ Source code https://github.com/bluesky/ophyd-async
102
+ Documentation https://blueskyproject.io/ophyd-async
103
+ ============== ==============================================================
109
104
 
110
- Many facilities use ophyd to integrate with control systems that use `EPICS`_ ,
111
- but ophyd's design and some of its objects are also used to integrate with
112
- other control systems.
105
+ Python library for asynchronously interfacing with hardware, intended to
106
+ be used as an abstraction layer that enables experiment orchestration and data
107
+ acquisition code to operate above the specifics of particular devices and control
108
+ systems.
113
109
 
114
- * Put the details specific to a device or control system behind a **high-level
115
- interface** with methods like ``trigger()``, ``read()``, and ``set(...)``.
116
- * **Group** individual control channels (such as EPICS V3 PVs) into logical
117
- "Devices" to be configured and used as units with internal coordination.
118
- * Assign readings with **names meaningful for data analysis** that will
119
- propagate into metadata.
120
- * **Categorize** readings by "kind" (primary reading, configuration,
121
- engineering/debugging) which can be read selectively.
110
+ Both ophyd and ophyd-async are typically used with the `Bluesky Run Engine`_ for
111
+ experiment orchestration and data acquisition. However, these libraries are
112
+ able to be used in a stand-alone fashion. For an example of how a facility defines
113
+ and uses ophyd-async devices, see `dls-dodal`_, which is currently using a
114
+ mixture of ophyd and ophyd-async devices.
122
115
 
123
- ============== ==============================================================
124
- PyPI ``pip install ophyd``
125
- Conda ``conda install -c conda-forge ophyd``
126
- Source code https://github.com/bluesky/ophyd
127
- Documentation https://blueskyproject.io/ophyd
128
- ============== ==============================================================
116
+ While `EPICS`_ is the most common control system layer that ophyd-async can
117
+ interface with, other control systems like `Tango`_ are used by some facilities
118
+ also. In addition to the abstractions provided by ophyd, ophyd-async allows:
119
+
120
+ * Asynchronous signal access, opening the possibility for hardware-triggered
121
+ scanning (also known as fly-scanning)
122
+ * Simpler instantiation of devices (groupings of signals) with less reliance
123
+ upon complex class hierarchies
124
+
125
+ NOTE: ophyd-async is included on a provisional basis until the v1.0 release.
129
126
 
130
127
  See the tutorials for usage examples.
131
128
 
132
- .. |build_status| image:: https://github.com/bluesky/ophyd/workflows/Unit%20Tests/badge.svg?branch=master
133
- :target: https://github.com/bluesky/ophyd/actions?query=workflow%3A%22Unit+Tests%22
134
- :alt: Build Status
129
+ .. |code_ci| image:: https://github.com/bluesky/ophyd-async/actions/workflows/code.yml/badge.svg?branch=main
130
+ :target: https://github.com/bluesky/ophyd-async/actions/workflows/code.yml
131
+ :alt: Code CI
132
+
133
+ .. |docs_ci| image:: https://github.com/bluesky/ophyd-async/actions/workflows/docs.yml/badge.svg?branch=main
134
+ :target: https://github.com/bluesky/ophyd-async/actions/workflows/docs.yml
135
+ :alt: Docs CI
135
136
 
136
- .. |coverage| image:: https://codecov.io/gh/bluesky/ophyd/branch/master/graph/badge.svg
137
- :target: https://codecov.io/gh/bluesky/ophyd
137
+ .. |coverage| image:: https://codecov.io/gh/bluesky/ophyd-async/branch/master/graph/badge.svg
138
+ :target: https://codecov.io/gh/bluesky/ophyd-async
138
139
  :alt: Test Coverage
139
140
 
140
- .. |pypi_version| image:: https://img.shields.io/pypi/v/ophyd.svg
141
- :target: https://pypi.org/project/ophyd
141
+ .. |pypi_version| image:: https://img.shields.io/pypi/v/ophyd-async.svg
142
+ :target: https://pypi.org/project/ophyd-async
142
143
  :alt: Latest PyPI version
143
144
 
144
145
  .. |license| image:: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg
@@ -147,8 +148,14 @@ See the tutorials for usage examples.
147
148
 
148
149
  .. _Bluesky Run Engine: http://blueskyproject.io/bluesky
149
150
 
151
+ .. _Ophyd: http://blueskyproject.io/ophyd
152
+
153
+ .. _dls-dodal: https://github.com/DiamondLightSource/dodal
154
+
150
155
  .. _EPICS: http://www.aps.anl.gov/epics/
151
156
 
157
+ .. _Tango: https://www.tango-controls.org/
158
+
152
159
  ..
153
160
  Anything below this line is used when viewing README.rst and will be replaced
154
161
  when included in index.rst
@@ -0,0 +1,56 @@
1
+ ophyd_async/__init__.py,sha256=WJoRU7gO-hRzyf7a-C952zF6-7zwPjP7qZ1Qu5GTUC8,124
2
+ ophyd_async/__main__.py,sha256=G-Zcv_G9zK7Nhx6o5L5w-wyhMxdl_WgyMELu8IMFqAE,328
3
+ ophyd_async/_version.py,sha256=nSi77MfFtahju9XLwmhCTVsEygz__it8HHOvmBHSgKI,408
4
+ ophyd_async/core/__init__.py,sha256=Vy0m53wHwRSQ8FVeqzyZ5c6qdWIkkLA4vmEwbdk7iqc,2121
5
+ ophyd_async/core/_providers.py,sha256=HJbXQGocNxGDr5fv13zOkONXN1JqCfqJ3P0pR_sIy1Y,2137
6
+ ophyd_async/core/async_status.py,sha256=-sfIf7VhwAP25kSVwKZjAIYOTROpfnh2jgkDw5_afSU,2801
7
+ ophyd_async/core/detector.py,sha256=-KttT_gPniRB5xalfh5xj2aH5nLV6KoDVLlXaM-d0rw,9989
8
+ ophyd_async/core/device.py,sha256=Ai5FRThlDeXnzKwTuVlmD8RFzlF_cq88Y6xcEiHOTho,5545
9
+ ophyd_async/core/device_save_loader.py,sha256=RXA3dPUPihAR2ZGDStlGiA-TAsr_xqL0snsCjMsMnfA,9138
10
+ ophyd_async/core/flyer.py,sha256=PBfxH9XMYqhFzNFqR2H6U_FxY2L5Un5mUD_3GMDZvdo,2780
11
+ ophyd_async/core/signal.py,sha256=PgoUzcmMaQczoBSlHDA4AmRKoDwoDH9uNmF_5DgJa6k,11800
12
+ ophyd_async/core/signal_backend.py,sha256=hwrDrwJK9rzOW4fsN_T2RFdCHhYwSFkR325i5L0lHE8,1363
13
+ ophyd_async/core/sim_signal_backend.py,sha256=h7if2Oqr4jNOLfQoD_sqJOZd3uyTNHpfn1oJ4apug84,5590
14
+ ophyd_async/core/standard_readable.py,sha256=9cBetIYttAho-7wOB3T1YgSJy0iWEFTAdn9jZkrGvLA,2560
15
+ ophyd_async/core/utils.py,sha256=10cGVTR73oa_05ZVfdNzdTlr3-s1VHxvgtT1PD5LJ8Q,4585
16
+ ophyd_async/epics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ ophyd_async/epics/pvi.py,sha256=BGhAXLyY2Z2e3ujyJ2ObnW19hogGvTZbHZY6Iq_nQeU,2331
18
+ ophyd_async/epics/_backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ ophyd_async/epics/_backend/_aioca.py,sha256=0PmpDNohm52461plfrxirLJsyhZXgYIDJv12Y6zfQ0A,8731
20
+ ophyd_async/epics/_backend/_p4p.py,sha256=aikjxQUgBRqNmMNMzOx3-N0rPdn-D1E8P5vQwvmYIgM,11970
21
+ ophyd_async/epics/_backend/common.py,sha256=3zxGLNcBtss6WUGAwfAjxWnUF4YbFbYvpOUmoFJaG5Y,753
22
+ ophyd_async/epics/areadetector/__init__.py,sha256=oEOzL7gEVVSULeT2WkFrva8lUKeX0zmQD0tmwFz6L24,325
23
+ ophyd_async/epics/areadetector/single_trigger_det.py,sha256=q5mG-OUVagIjvXLb28lsrGj4eUSoH2pNW2rT4rQR8fA,1206
24
+ ophyd_async/epics/areadetector/utils.py,sha256=dez54oElIkGMnhSM9qghToUB1opSqjdWTV2vhHCgRMA,3133
25
+ ophyd_async/epics/areadetector/controllers/__init__.py,sha256=UG2-M5d2ykp2T8isQJCbAsGZF1aH0BtC_OPlzzPTjnA,149
26
+ ophyd_async/epics/areadetector/controllers/ad_sim_controller.py,sha256=mthZ6WxajMEgUKptq3bnkIctbLhjzTagV66i1auB8cg,1587
27
+ ophyd_async/epics/areadetector/controllers/pilatus_controller.py,sha256=TSWry-eWfDl4LqafMH75aVqYTrsdmSEbBzCLDUcR2aY,1552
28
+ ophyd_async/epics/areadetector/drivers/__init__.py,sha256=AOpIEYfoBhG9Nc4-SId99v4PpyEh4_RBXfNaqiXlwUI,315
29
+ ophyd_async/epics/areadetector/drivers/ad_base.py,sha256=ikfyNcZwJa5ah52DckjrBzkMMT_eDY1smM4XWfb6A6E,3689
30
+ ophyd_async/epics/areadetector/drivers/pilatus_driver.py,sha256=cn5WNz913UOnOttw2bssjV2Bo3p9SuJma3ckRyCdvw8,442
31
+ ophyd_async/epics/areadetector/writers/__init__.py,sha256=tpPcrYd1hs8WS7C0gmCnR2EBwjE5RzCljI7WwZ2V_LM,191
32
+ ophyd_async/epics/areadetector/writers/_hdfdataset.py,sha256=E0C9VgsPyY35h7k0mvcIhjsIVNavApLxizqNWlM388w,167
33
+ ophyd_async/epics/areadetector/writers/_hdffile.py,sha256=Zh7nWzK9-q0ASCi88tLAFUuWRL_rbz8XTNzKXwRmi88,1797
34
+ ophyd_async/epics/areadetector/writers/hdf_writer.py,sha256=lJHP1CzpXYH82GcgEA-D4qI27Uj1_W8pAVgexFSXmgk,5011
35
+ ophyd_async/epics/areadetector/writers/nd_file_hdf.py,sha256=rutCstILCGGwhP5pH_2lWM2QUcZ88-uxx5dTZIJUMWQ,1562
36
+ ophyd_async/epics/areadetector/writers/nd_plugin.py,sha256=l0yBBEazviyFsWJv_4_sfGn_YM_Iyd0_SlMdAmUlXDU,871
37
+ ophyd_async/epics/demo/__init__.py,sha256=DbVO4ufJWjQnZteilW8SBs5A8DN-Xajn0YibM0q8UkE,5500
38
+ ophyd_async/epics/demo/demo_ad_sim_detector.py,sha256=06y65yvaqXvL2rDocjYyLz9kTVzuwV-LeuPhEfExdOA,944
39
+ ophyd_async/epics/demo/mover.db,sha256=RFz0rxZue689Wh1sWTZwWeFMUrH04ttPq2u5xJH_Fp4,998
40
+ ophyd_async/epics/demo/sensor.db,sha256=AVtiydrdtwAz2EFurO2Ult9SSRtre3r0akOBbL98LT0,554
41
+ ophyd_async/epics/motion/__init__.py,sha256=tnmVRIwKa9PdN_xonJdAUD04UpEceh-hoD7XI62yDB0,46
42
+ ophyd_async/epics/motion/motor.py,sha256=lQcA3PSPxA6XOnk8y5VuJGnKTq2VjkcO6ldCrssGr3M,3392
43
+ ophyd_async/epics/signal/__init__.py,sha256=wb93RTqvSbGKVFQj8OHykbVLGLmwKHU72oi5xYu2UaY,188
44
+ ophyd_async/epics/signal/_epics_transport.py,sha256=DEIL0iYUAWssysVEgWGu1fHSM1l-ATV2kjUgPtDN9LY,858
45
+ ophyd_async/epics/signal/signal.py,sha256=7GnGa4CgFgTwyHeh4wYOJ2GEWwdXsC6vHD3z9LsaHaM,2543
46
+ ophyd_async/panda/__init__.py,sha256=rVM7PdHnON5h0Hcpz_49N5bVfcNmSH5V6ApdaGcBSno,533
47
+ ophyd_async/panda/panda.py,sha256=f_bEYUn7HwMCfVsZxPJ43e4Mo9FQrluHh7_vOHErwYE,8335
48
+ ophyd_async/panda/panda_controller.py,sha256=CK9TTXAGXne31C3HZfBo_grk9pIM1y9Lyo0lms37dLY,1236
49
+ ophyd_async/panda/table.py,sha256=dLoRP4zYNOkD_s0Vkp2wVYAwkjVG8nNdf8-FaXOTfPo,5655
50
+ ophyd_async/panda/utils.py,sha256=VHW5kPVISyEkmse_qQcyisBkkEwMO6GG2Ago-CH1AFA,487
51
+ ophyd_async-0.3a1.dist-info/LICENSE,sha256=pU5shZcsvWgz701EbT7yjFZ8rMvZcWgRH54CRt8ld_c,1517
52
+ ophyd_async-0.3a1.dist-info/METADATA,sha256=Ypyn4I7_5JESeLA3lNZUyld4MB3vNrWGw_bpTnYvq0g,7208
53
+ ophyd_async-0.3a1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
54
+ ophyd_async-0.3a1.dist-info/entry_points.txt,sha256=O0YNJTEufO0w9BozXi-JurTy2U1_o0ypeCgJLQ727Jk,58
55
+ ophyd_async-0.3a1.dist-info/top_level.txt,sha256=-hjorMsv5Rmjo3qrgqhjpal1N6kW5vMxZO3lD4iEaXs,12
56
+ ophyd_async-0.3a1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
File without changes
File without changes
File without changes
@@ -1,60 +0,0 @@
1
- """Base device"""
2
- from __future__ import annotations
3
-
4
- from typing import Iterator, Optional, Tuple
5
-
6
- from bluesky.protocols import HasName
7
-
8
- from ..utils import wait_for_connection
9
-
10
-
11
- class Device(HasName):
12
- """Common base class for all Ophyd Async Devices.
13
-
14
- By default, names and connects all Device children.
15
- """
16
-
17
- _name: str = ""
18
- #: The parent Device if it exists
19
- parent: Optional[Device] = None
20
-
21
- def __init__(self, name: str = "") -> None:
22
- self.set_name(name)
23
-
24
- @property
25
- def name(self) -> str:
26
- """Return the name of the Device"""
27
- return self._name
28
-
29
- def children(self) -> Iterator[Tuple[str, Device]]:
30
- for attr_name, attr in self.__dict__.items():
31
- if attr_name != "parent" and isinstance(attr, Device):
32
- yield attr_name, attr
33
-
34
- def set_name(self, name: str):
35
- """Set ``self.name=name`` and each ``self.child.name=name+"-child"``.
36
-
37
- Parameters
38
- ----------
39
- name:
40
- New name to set
41
- """
42
- self._name = name
43
- for attr_name, child in self.children():
44
- child_name = f"{name}-{attr_name.rstrip('_')}" if name else ""
45
- child.set_name(child_name)
46
- child.parent = self
47
-
48
- async def connect(self, sim: bool = False):
49
- """Connect self and all child Devices.
50
-
51
- Parameters
52
- ----------
53
- sim:
54
- If True then connect in simulation mode.
55
- """
56
- coros = {
57
- name: child_device.connect(sim) for name, child_device in self.children()
58
- }
59
- if coros:
60
- await wait_for_connection(**coros)
@@ -1,121 +0,0 @@
1
- """Interface for connecting and naming multiple devices"""
2
- import asyncio
3
- import logging
4
- import sys
5
- from contextlib import suppress
6
- from typing import Any, Dict, Set
7
-
8
- from bluesky.run_engine import call_in_bluesky_event_loop
9
-
10
- from ..utils import NotConnected
11
- from .device import Device
12
-
13
-
14
- class DeviceCollector:
15
- """Collector of top level Device instances to be used as a context manager
16
-
17
- Parameters
18
- ----------
19
- set_name:
20
- If True, call ``device.set_name(variable_name)`` on all collected
21
- Devices
22
- connect:
23
- If True, call ``device.connect(sim)`` in parallel on all
24
- collected Devices
25
- sim:
26
- If True, connect Signals in simulation mode
27
- timeout:
28
- How long to wait for connect before logging an exception
29
-
30
- Notes
31
- -----
32
- Example usage::
33
-
34
- [async] with DeviceCollector():
35
- t1x = motor.Motor("BLxxI-MO-TABLE-01:X")
36
- t1y = motor.Motor("pva://BLxxI-MO-TABLE-01:Y")
37
- # Names and connects devices here
38
- assert t1x.comm.velocity.source
39
- assert t1x.name == "t1x"
40
-
41
- """
42
-
43
- def __init__(
44
- self,
45
- set_name=True,
46
- connect=True,
47
- sim=False,
48
- timeout: float = 10.0,
49
- ):
50
- self._set_name = set_name
51
- self._connect = connect
52
- self._sim = sim
53
- self._timeout = timeout
54
- self._names_on_enter: Set[str] = set()
55
- self._objects_on_exit: Dict[str, Any] = {}
56
-
57
- def _caller_locals(self):
58
- """Walk up until we find a stack frame that doesn't have us as self"""
59
- try:
60
- raise ValueError
61
- except ValueError:
62
- _, _, tb = sys.exc_info()
63
- assert tb, "Can't get traceback, this shouldn't happen"
64
- caller_frame = tb.tb_frame
65
- while caller_frame.f_locals.get("self", None) is self:
66
- caller_frame = caller_frame.f_back
67
- return caller_frame.f_locals
68
-
69
- def __enter__(self) -> "DeviceCollector":
70
- # Stash the names that were defined before we were called
71
- self._names_on_enter = set(self._caller_locals())
72
- return self
73
-
74
- async def __aenter__(self) -> "DeviceCollector":
75
- return self.__enter__()
76
-
77
- async def _on_exit(self) -> None:
78
- # Name and kick off connect for devices
79
- tasks: Dict[asyncio.Task, str] = {}
80
- for name, obj in self._objects_on_exit.items():
81
- if name not in self._names_on_enter and isinstance(obj, Device):
82
- if self._set_name and not obj.name:
83
- obj.set_name(name)
84
- if self._connect:
85
- task = asyncio.create_task(obj.connect(self._sim))
86
- tasks[task] = name
87
- # Wait for all the signals to have finished
88
- if tasks:
89
- await self._wait_for_tasks(tasks)
90
-
91
- async def _wait_for_tasks(self, tasks: Dict[asyncio.Task, str]):
92
- done, pending = await asyncio.wait(tasks, timeout=self._timeout)
93
- if pending:
94
- msg = f"{len(pending)} Devices did not connect:"
95
- for t in pending:
96
- t.cancel()
97
- with suppress(Exception):
98
- await t
99
- e = t.exception()
100
- msg += f"\n {tasks[t]}: {type(e).__name__}"
101
- lines = str(e).splitlines()
102
- if len(lines) <= 1:
103
- msg += f": {e}"
104
- else:
105
- msg += "".join(f"\n {line}" for line in lines)
106
- logging.error(msg)
107
- raised = [t for t in done if t.exception()]
108
- if raised:
109
- logging.error(f"{len(raised)} Devices raised an error:")
110
- for t in raised:
111
- logging.exception(f" {tasks[t]}:", exc_info=t.exception())
112
- if pending or raised:
113
- raise NotConnected("Not all Devices connected")
114
-
115
- async def __aexit__(self, type, value, traceback):
116
- self._objects_on_exit = self._caller_locals()
117
- await self._on_exit()
118
-
119
- def __exit__(self, type_, value, traceback):
120
- self._objects_on_exit = self._caller_locals()
121
- return call_in_bluesky_event_loop(self._on_exit())
@@ -1,14 +0,0 @@
1
- """Dictionary which can contain mappings between integers and devices."""
2
-
3
- from typing import Dict, Generator, Tuple, TypeVar
4
-
5
- from .device import Device
6
-
7
- VT = TypeVar("VT", bound=Device)
8
-
9
-
10
- class DeviceVector(Dict[int, VT], Device):
11
- def children(self) -> Generator[Tuple[str, Device], None, None]:
12
- for attr_name, attr in self.items():
13
- if isinstance(attr, Device):
14
- yield str(attr_name), attr
@@ -1,18 +0,0 @@
1
- from ophyd_async.core import Device
2
-
3
- from ..signal.signal import epics_signal_rw
4
- from .utils import ImageMode, ad_r, ad_rw
5
-
6
-
7
- class ADDriver(Device):
8
- def __init__(self, prefix: str) -> None:
9
- # Define some signals
10
- self.acquire = ad_rw(bool, prefix + "Acquire")
11
- self.acquire_time = ad_rw(float, prefix + "AcquireTime")
12
- self.num_images = ad_rw(int, prefix + "NumImages")
13
- self.image_mode = ad_rw(ImageMode, prefix + "ImageMode")
14
- self.array_counter = ad_rw(int, prefix + "ArrayCounter")
15
- self.array_size_x = ad_r(int, prefix + "ArraySizeX")
16
- self.array_size_y = ad_r(int, prefix + "ArraySizeY")
17
- # There is no _RBV for this one
18
- self.wait_for_plugins = epics_signal_rw(bool, prefix + "WaitForPlugins")
@@ -1,18 +0,0 @@
1
- import tempfile
2
- from abc import abstractmethod
3
- from pathlib import Path
4
- from typing import Protocol
5
-
6
-
7
- class DirectoryProvider(Protocol):
8
- @abstractmethod
9
- async def get_directory(self) -> Path:
10
- ...
11
-
12
-
13
- class TmpDirectoryProvider(DirectoryProvider):
14
- def __init__(self) -> None:
15
- self._directory = Path(tempfile.mkdtemp())
16
-
17
- async def get_directory(self) -> Path:
18
- return self._directory
@@ -1,167 +0,0 @@
1
- import asyncio
2
- import collections
3
- import time
4
- from typing import Callable, Dict, Iterator, Optional, Sized
5
-
6
- from bluesky.protocols import (
7
- Asset,
8
- Descriptor,
9
- Flyable,
10
- PartialEvent,
11
- WritesExternalAssets,
12
- )
13
- from bluesky.utils import new_uid
14
- from event_model import compose_stream_resource
15
-
16
- from ophyd_async.core import (
17
- DEFAULT_TIMEOUT,
18
- AsyncStatus,
19
- StandardReadable,
20
- set_and_wait_for_value,
21
- )
22
-
23
- from .ad_driver import ADDriver
24
- from .directory_provider import DirectoryProvider
25
- from .nd_file_hdf import NDFileHDF
26
- from .utils import FileWriteMode, ImageMode
27
-
28
- # How long in seconds to wait between flushes of HDF datasets
29
- FLUSH_PERIOD = 0.5
30
-
31
- # How long to wait for new frames before timing out
32
- FRAME_TIMEOUT = 120
33
-
34
-
35
- class _HDFResource:
36
- def __init__(self) -> None:
37
- # TODO: set to Deque[Asset] after protocols updated for stream*
38
- # https://github.com/bluesky/bluesky/issues/1558
39
- self.asset_docs = collections.deque() # type: ignore
40
- self._last_emitted = 0
41
- self._last_flush = time.monotonic()
42
- self._compose_datum: Optional[Callable] = None
43
-
44
- def _append_resource(self, full_file_name: str):
45
- resource_doc, (self._compose_datum,) = compose_stream_resource(
46
- spec="AD_HDF5_SWMR_SLICE",
47
- root="/",
48
- resource_path=full_file_name,
49
- resource_kwargs={},
50
- stream_names=["primary"],
51
- )
52
- self.asset_docs.append(("stream_resource", resource_doc))
53
-
54
- def _append_datum(self, event_count: int):
55
- assert self._compose_datum, "Resource not emitted yet"
56
- datum_doc = self._compose_datum(
57
- datum_kwargs={},
58
- event_offset=self._last_emitted,
59
- event_count=event_count,
60
- )
61
- self._last_emitted += event_count
62
- self.asset_docs.append(("stream_datum", datum_doc))
63
-
64
- async def flush_and_publish(self, hdf: NDFileHDF):
65
- num_captured = await hdf.num_captured.get_value()
66
- if num_captured:
67
- if self._compose_datum is None:
68
- self._append_resource(await hdf.full_file_name.get_value())
69
- event_count = num_captured - self._last_emitted
70
- if event_count:
71
- self._append_datum(event_count)
72
- await hdf.flush_now.set(True)
73
- self._last_flush = time.monotonic()
74
- if time.monotonic() - self._last_flush > FRAME_TIMEOUT:
75
- raise TimeoutError(f"{hdf.name}: writing stalled on frame {num_captured}")
76
-
77
-
78
- class HDFStreamerDet(StandardReadable, Flyable, WritesExternalAssets):
79
- def __init__(
80
- self, drv: ADDriver, hdf: NDFileHDF, dp: DirectoryProvider, name=""
81
- ) -> None:
82
- self.drv = drv
83
- self.hdf = hdf
84
- self._dp = dp
85
- self._resource = _HDFResource()
86
- self._capture_status: Optional[AsyncStatus] = None
87
- self._start_status: Optional[AsyncStatus] = None
88
- self.set_readable_signals(config=[self.drv.acquire_time])
89
- super().__init__(name)
90
-
91
- @AsyncStatus.wrap
92
- async def stage(self) -> None:
93
- # Make a new resource for the new HDF file we're going to open
94
- self._resource = _HDFResource()
95
- await asyncio.gather(
96
- self.drv.wait_for_plugins.set(True),
97
- self.hdf.lazy_open.set(True),
98
- self.hdf.swmr_mode.set(True),
99
- self.hdf.file_path.set(str(await self._dp.get_directory())),
100
- self.hdf.file_name.set(f"{self.name}-{new_uid()}"),
101
- self.hdf.file_template.set("%s/%s.h5"),
102
- # Go forever
103
- self.hdf.num_capture.set(0),
104
- self.hdf.file_write_mode.set(FileWriteMode.stream),
105
- )
106
- # Wait for it to start, stashing the status that tells us when it finishes
107
- self._capture_status = await set_and_wait_for_value(self.hdf.capture, True)
108
- await super().stage()
109
-
110
- async def describe(self) -> Dict[str, Descriptor]:
111
- datakeys = await super().describe()
112
- # Insert a descriptor for the HDF resource, this will not appear
113
- # in read() as it describes StreamResource outputs only
114
- datakeys[self.name] = Descriptor(
115
- source=self.hdf.full_file_name.source,
116
- shape=await asyncio.gather(
117
- self.drv.array_size_y.get_value(),
118
- self.drv.array_size_x.get_value(),
119
- ),
120
- dtype="array",
121
- external="STREAM:",
122
- )
123
- return datakeys
124
-
125
- # For step scan, take a single frame
126
- @AsyncStatus.wrap
127
- async def trigger(self):
128
- await self.drv.image_mode.set(ImageMode.single)
129
- frame_timeout = DEFAULT_TIMEOUT + await self.drv.acquire_time.get_value()
130
- await self.drv.acquire.set(1, timeout=frame_timeout)
131
- await self._resource.flush_and_publish(self.hdf)
132
-
133
- def collect_asset_docs(self) -> Iterator[Asset]:
134
- while self._resource.asset_docs:
135
- yield self._resource.asset_docs.popleft()
136
-
137
- # For flyscan, take the number of frames we wanted
138
- @AsyncStatus.wrap
139
- async def kickoff(self) -> None:
140
- await self.drv.image_mode.set(ImageMode.multiple)
141
- # Wait for it to start, stashing the status that tells us when it finishes
142
- self._start_status = await set_and_wait_for_value(self.drv.acquire, True)
143
-
144
- # Do the same thing for flyscans and step scans
145
- async def describe_collect(self) -> Dict[str, Dict[str, Descriptor]]:
146
- return {self.name: await self.describe()}
147
-
148
- def collect(self) -> Iterator[PartialEvent]:
149
- yield from iter([])
150
-
151
- @AsyncStatus.wrap
152
- async def complete(self) -> None:
153
- done: Sized = ()
154
- while not done:
155
- assert self._start_status, "Kickoff not run"
156
- done, _ = await asyncio.wait(
157
- (self._start_status.task,), timeout=FLUSH_PERIOD
158
- )
159
- await self._resource.flush_and_publish(self.hdf)
160
-
161
- @AsyncStatus.wrap
162
- async def unstage(self) -> None:
163
- # Already done a caput callback in _capture_status, so can't do one here
164
- await self.hdf.capture.set(False, wait=False)
165
- assert self._capture_status, "Stage not run"
166
- await self._capture_status
167
- await super().unstage()
@@ -1,13 +0,0 @@
1
- from ophyd_async.core import Device
2
-
3
- from .utils import ad_r
4
-
5
-
6
- class NDPlugin(Device):
7
- pass
8
-
9
-
10
- class NDPluginStats(NDPlugin):
11
- def __init__(self, prefix: str) -> None:
12
- # Define some signals
13
- self.unique_id = ad_r(int, prefix + "UniqueId")
@@ -1,22 +0,0 @@
1
- from typing import Dict, TypedDict
2
-
3
- from p4p.client.asyncio import Context
4
-
5
-
6
- class PVIEntry(TypedDict, total=False):
7
- d: str
8
- r: str
9
- rw: str
10
- w: str
11
- x: str
12
-
13
-
14
- async def pvi_get(pv: str, ctxt: Context, timeout: float = 5.0) -> Dict[str, PVIEntry]:
15
- pv_info = ctxt.get(pv, timeout=timeout).get("pvi").todict()
16
-
17
- result = {}
18
-
19
- for attr_name, attr_info in pv_info.items():
20
- result[attr_name] = PVIEntry(**attr_info) # type: ignore
21
-
22
- return result