ophyd-async 0.6.0__tar.gz → 0.7.0__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 (240) hide show
  1. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/PKG-INFO +5 -1
  2. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/examples/foo_detector.py +2 -2
  3. ophyd_async-0.7.0/docs/examples/tango_demo.py +54 -0
  4. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/explanations/decisions/0007-subpackage-structure.md +1 -1
  5. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/how-to/make-a-standard-detector.rst +5 -5
  6. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/pyproject.toml +3 -0
  7. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/_version.py +2 -2
  8. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/__init__.py +4 -4
  9. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_detector.py +74 -37
  10. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_device.py +6 -1
  11. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_flyer.py +5 -20
  12. ophyd_async-0.7.0/src/ophyd_async/core/_table.py +146 -0
  13. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adaravis/_aravis_controller.py +4 -4
  14. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adcore/_core_logic.py +2 -2
  15. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adkinetix/_kinetix_controller.py +3 -3
  16. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adpilatus/_pilatus_controller.py +5 -3
  17. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adsimdetector/_sim.py +1 -1
  18. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adsimdetector/_sim_controller.py +3 -3
  19. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/advimba/_vimba_controller.py +3 -3
  20. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/eiger/_eiger_controller.py +3 -3
  21. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/fastcs/panda/_block.py +7 -0
  22. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/fastcs/panda/_control.py +2 -2
  23. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/fastcs/panda/_table.py +3 -37
  24. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/fastcs/panda/_trigger.py +3 -3
  25. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/fastcs/panda/_writer.py +2 -2
  26. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/plan_stubs/_fly.py +1 -3
  27. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +5 -3
  28. ophyd_async-0.7.0/src/ophyd_async/tango/__init__.py +45 -0
  29. ophyd_async-0.7.0/src/ophyd_async/tango/base_devices/__init__.py +4 -0
  30. ophyd_async-0.7.0/src/ophyd_async/tango/base_devices/_base_device.py +225 -0
  31. ophyd_async-0.7.0/src/ophyd_async/tango/base_devices/_tango_readable.py +33 -0
  32. ophyd_async-0.7.0/src/ophyd_async/tango/demo/__init__.py +12 -0
  33. ophyd_async-0.7.0/src/ophyd_async/tango/demo/_counter.py +37 -0
  34. ophyd_async-0.7.0/src/ophyd_async/tango/demo/_detector.py +42 -0
  35. ophyd_async-0.7.0/src/ophyd_async/tango/demo/_mover.py +77 -0
  36. ophyd_async-0.7.0/src/ophyd_async/tango/demo/_tango/__init__.py +3 -0
  37. ophyd_async-0.7.0/src/ophyd_async/tango/demo/_tango/_servers.py +108 -0
  38. ophyd_async-0.7.0/src/ophyd_async/tango/signal/__init__.py +39 -0
  39. ophyd_async-0.7.0/src/ophyd_async/tango/signal/_signal.py +223 -0
  40. ophyd_async-0.7.0/src/ophyd_async/tango/signal/_tango_transport.py +764 -0
  41. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async.egg-info/PKG-INFO +5 -1
  42. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async.egg-info/SOURCES.txt +16 -2
  43. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async.egg-info/requires.txt +5 -0
  44. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/system_tests/epics/eiger/test_eiger_system.py +1 -1
  45. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/conftest.py +10 -2
  46. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_device_collector.py +10 -0
  47. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_flyer.py +200 -61
  48. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/adaravis/test_aravis.py +3 -11
  49. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/adcore/test_drivers.py +4 -4
  50. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/adcore/test_scans.py +9 -6
  51. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/adkinetix/test_kinetix.py +4 -11
  52. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/adpilatus/test_pilatus.py +40 -27
  53. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/adsimdetector/test_sim.py +95 -95
  54. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/advimba/test_vimba.py +6 -11
  55. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/conftest.py +22 -4
  56. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/eiger/test_eiger_controller.py +4 -4
  57. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/eiger/test_eiger_detector.py +1 -1
  58. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/fastcs/panda/test_hdf_panda.py +3 -119
  59. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/fastcs/panda/test_panda_control.py +3 -3
  60. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/fastcs/panda/test_panda_utils.py +1 -0
  61. ophyd_async-0.7.0/tests/fastcs/panda/test_table.py +385 -0
  62. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/fastcs/panda/test_trigger.py +21 -8
  63. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/plan_stubs/test_fly.py +16 -12
  64. ophyd_async-0.7.0/tests/tango/test_base_device.py +400 -0
  65. ophyd_async-0.7.0/tests/tango/test_tango_signals.py +775 -0
  66. ophyd_async-0.7.0/tests/tango/test_tango_transport.py +854 -0
  67. ophyd_async-0.6.0/src/ophyd_async/core/_table.py +0 -63
  68. ophyd_async-0.6.0/tests/epics/adpilatus/test_pilatus_controller.py +0 -46
  69. ophyd_async-0.6.0/tests/epics/adsimdetector/test_adsim_controller.py +0 -32
  70. ophyd_async-0.6.0/tests/fastcs/panda/test_table.py +0 -232
  71. ophyd_async-0.6.0/tests/sim/demo/__init__.py +0 -0
  72. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.codecov.yml +0 -0
  73. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.copier-answers.yml +0 -0
  74. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.devcontainer/devcontainer.json +0 -0
  75. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.git-blame-ignore-revs +0 -0
  76. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/CONTRIBUTING.md +0 -0
  77. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  78. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/ISSUE_TEMPLATE/issue.md +0 -0
  79. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -0
  80. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/actions/install_requirements/action.yml +0 -0
  81. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/dependabot.yml +0 -0
  82. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/pages/index.html +0 -0
  83. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/pages/make_switcher.py +0 -0
  84. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/workflows/_check.yml +0 -0
  85. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/workflows/_dist.yml +0 -0
  86. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/workflows/_docs.yml +0 -0
  87. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/workflows/_pypi.yml +0 -0
  88. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/workflows/_release.yml +0 -0
  89. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/workflows/_test.yml +0 -0
  90. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/workflows/_tox.yml +0 -0
  91. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/workflows/ci.yml +0 -0
  92. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.github/workflows/periodic.yml +0 -0
  93. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.gitignore +0 -0
  94. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.mailmap +0 -0
  95. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/.pre-commit-config.yaml +0 -0
  96. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/Dockerfile +0 -0
  97. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/LICENSE +0 -0
  98. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/README.md +0 -0
  99. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/_api.rst +0 -0
  100. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/_templates/custom-module-template.rst +0 -0
  101. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/conf.py +0 -0
  102. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/examples/epics_demo.py +0 -0
  103. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/explanations/decisions/0001-record-architecture-decisions.md +0 -0
  104. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/explanations/decisions/0002-switched-to-python-copier-template.md +0 -0
  105. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/explanations/decisions/0003-ophyd-async-migration.rst +0 -0
  106. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/explanations/decisions/0004-repository-structure.rst +0 -0
  107. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/explanations/decisions/0005-respect-black-line-length.rst +0 -0
  108. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/explanations/decisions/0006-procedural-device-definitions.rst +0 -0
  109. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/explanations/decisions/COPYME +0 -0
  110. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/explanations/decisions.md +0 -0
  111. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/explanations/design-goals.rst +0 -0
  112. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/explanations/event-loop-choice.rst +0 -0
  113. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/explanations/flyscanning.rst +0 -0
  114. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/explanations.md +0 -0
  115. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/genindex.rst +0 -0
  116. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/how-to/choose-interfaces-for-devices.md +0 -0
  117. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/how-to/compound-devices.rst +0 -0
  118. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/how-to/contribute.md +0 -0
  119. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/how-to/make-a-simple-device.rst +0 -0
  120. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/how-to/write-tests-for-devices.rst +0 -0
  121. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/how-to.md +0 -0
  122. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/images/bluesky_ophyd_epics_devices_logo.svg +0 -0
  123. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/images/bluesky_ophyd_logo.svg +0 -0
  124. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/images/ophyd_favicon.svg +0 -0
  125. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/index.md +0 -0
  126. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/reference.md +0 -0
  127. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/tutorials/installation.md +0 -0
  128. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/tutorials/using-existing-devices.rst +0 -0
  129. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/docs/tutorials.md +0 -0
  130. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/setup.cfg +0 -0
  131. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/__init__.py +0 -0
  132. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/__main__.py +0 -0
  133. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_device_save_loader.py +0 -0
  134. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_hdf_dataset.py +0 -0
  135. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_log.py +0 -0
  136. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_mock_signal_backend.py +0 -0
  137. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_mock_signal_utils.py +0 -0
  138. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_protocol.py +0 -0
  139. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_providers.py +0 -0
  140. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_readable.py +0 -0
  141. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_signal.py +0 -0
  142. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_signal_backend.py +0 -0
  143. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_soft_signal_backend.py +0 -0
  144. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_status.py +0 -0
  145. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/core/_utils.py +0 -0
  146. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/__init__.py +0 -0
  147. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adaravis/__init__.py +0 -0
  148. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adaravis/_aravis.py +0 -0
  149. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adaravis/_aravis_io.py +0 -0
  150. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adcore/__init__.py +0 -0
  151. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adcore/_core_io.py +0 -0
  152. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adcore/_hdf_writer.py +0 -0
  153. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adcore/_single_trigger.py +0 -0
  154. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adcore/_utils.py +0 -0
  155. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adkinetix/__init__.py +0 -0
  156. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adkinetix/_kinetix.py +0 -0
  157. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adkinetix/_kinetix_io.py +0 -0
  158. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adpilatus/__init__.py +0 -0
  159. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adpilatus/_pilatus.py +0 -0
  160. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adpilatus/_pilatus_io.py +0 -0
  161. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/adsimdetector/__init__.py +0 -0
  162. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/advimba/__init__.py +0 -0
  163. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/advimba/_vimba.py +0 -0
  164. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/advimba/_vimba_io.py +0 -0
  165. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/demo/__init__.py +0 -0
  166. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/demo/_mover.py +0 -0
  167. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/demo/_sensor.py +0 -0
  168. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/demo/mover.db +0 -0
  169. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/demo/sensor.db +0 -0
  170. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/eiger/__init__.py +0 -0
  171. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/eiger/_eiger.py +0 -0
  172. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/eiger/_eiger_io.py +0 -0
  173. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/eiger/_odin_io.py +0 -0
  174. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/motor.py +0 -0
  175. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/pvi/__init__.py +0 -0
  176. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/pvi/_pvi.py +0 -0
  177. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/signal/__init__.py +0 -0
  178. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/signal/_aioca.py +0 -0
  179. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/signal/_common.py +0 -0
  180. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/signal/_epics_transport.py +0 -0
  181. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/signal/_p4p.py +0 -0
  182. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/epics/signal/_signal.py +0 -0
  183. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/fastcs/__init__.py +0 -0
  184. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/fastcs/odin/__init__.py +0 -0
  185. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/fastcs/panda/__init__.py +0 -0
  186. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/fastcs/panda/_hdf_panda.py +0 -0
  187. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/fastcs/panda/_utils.py +0 -0
  188. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/plan_stubs/__init__.py +0 -0
  189. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/plan_stubs/_ensure_connected.py +0 -0
  190. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/plan_stubs/_nd_attributes.py +0 -0
  191. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/sim/__init__.py +0 -0
  192. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/sim/demo/__init__.py +0 -0
  193. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/sim/demo/_pattern_detector/__init__.py +0 -0
  194. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +0 -0
  195. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_detector_writer.py +0 -0
  196. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +0 -0
  197. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/sim/demo/_sim_motor.py +0 -0
  198. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async/sim/testing/__init__.py +0 -0
  199. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async.egg-info/dependency_links.txt +0 -0
  200. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async.egg-info/entry_points.txt +0 -0
  201. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/src/ophyd_async.egg-info/top_level.txt +0 -0
  202. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/system_tests/epics/eiger/README.md +0 -0
  203. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/system_tests/epics/eiger/start_iocs_and_run_tests.sh +0 -0
  204. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_device.py +0 -0
  205. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_device_save_loader.py +0 -0
  206. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_log.py +0 -0
  207. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_mock_signal_backend.py +0 -0
  208. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_protocol.py +0 -0
  209. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_providers.py +0 -0
  210. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_readable.py +0 -0
  211. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_signal.py +0 -0
  212. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_soft_signal_backend.py +0 -0
  213. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_status.py +0 -0
  214. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_subset_enum.py +0 -0
  215. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_utils.py +0 -0
  216. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/core/test_watchable_async_status.py +0 -0
  217. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/adcore/test_single_trigger.py +0 -0
  218. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/adcore/test_writers.py +0 -0
  219. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/demo/test_demo.py +0 -0
  220. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/eiger/test_odin_io.py +0 -0
  221. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/pvi/test_pvi.py +0 -0
  222. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/signal/test_common.py +0 -0
  223. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/signal/test_records.db +0 -0
  224. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/signal/test_signals.py +0 -0
  225. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/test_areadetector_subclass_naming.py +0 -0
  226. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/epics/test_motor.py +0 -0
  227. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/fastcs/panda/db/panda.db +0 -0
  228. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/fastcs/panda/test_panda_connect.py +0 -0
  229. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/fastcs/panda/test_writer.py +0 -0
  230. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/plan_stubs/test_ensure_connected.py +0 -0
  231. {ophyd_async-0.6.0/src/ophyd_async/tango → ophyd_async-0.7.0/tests/sim}/__init__.py +0 -0
  232. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/sim/conftest.py +0 -0
  233. {ophyd_async-0.6.0/tests/sim → ophyd_async-0.7.0/tests/sim/demo}/__init__.py +0 -0
  234. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/sim/demo/test_sim_motor.py +0 -0
  235. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/sim/test_pattern_generator.py +0 -0
  236. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/sim/test_sim_detector.py +0 -0
  237. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/sim/test_sim_writer.py +0 -0
  238. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/sim/test_streaming_plan.py +0 -0
  239. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/test_cli.py +0 -0
  240. {ophyd_async-0.6.0 → ophyd_async-0.7.0}/tests/test_data/test_yaml_save.yml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ophyd-async
3
- Version: 0.6.0
3
+ Version: 0.7.0
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
@@ -57,10 +57,13 @@ Provides-Extra: pva
57
57
  Requires-Dist: p4p; extra == "pva"
58
58
  Provides-Extra: sim
59
59
  Requires-Dist: h5py; extra == "sim"
60
+ Provides-Extra: tango
61
+ Requires-Dist: pytango>=10.0.0; extra == "tango"
60
62
  Provides-Extra: dev
61
63
  Requires-Dist: ophyd_async[pva]; extra == "dev"
62
64
  Requires-Dist: ophyd_async[sim]; extra == "dev"
63
65
  Requires-Dist: ophyd_async[ca]; extra == "dev"
66
+ Requires-Dist: ophyd_async[tango]; extra == "dev"
64
67
  Requires-Dist: black; extra == "dev"
65
68
  Requires-Dist: flake8; extra == "dev"
66
69
  Requires-Dist: flake8-isort; extra == "dev"
@@ -83,6 +86,7 @@ Requires-Dist: pytest; extra == "dev"
83
86
  Requires-Dist: pytest-asyncio; extra == "dev"
84
87
  Requires-Dist: pytest-cov; extra == "dev"
85
88
  Requires-Dist: pytest-faulthandler; extra == "dev"
89
+ Requires-Dist: pytest-forked; extra == "dev"
86
90
  Requires-Dist: pytest-rerunfailures; extra == "dev"
87
91
  Requires-Dist: pytest-timeout; extra == "dev"
88
92
  Requires-Dist: ruff; extra == "dev"
@@ -4,7 +4,7 @@ from bluesky.protocols import HasHints, Hints
4
4
 
5
5
  from ophyd_async.core import (
6
6
  AsyncStatus,
7
- DetectorControl,
7
+ DetectorController,
8
8
  DetectorTrigger,
9
9
  PathProvider,
10
10
  StandardDetector,
@@ -19,7 +19,7 @@ class FooDriver(adcore.ADBaseIO):
19
19
  super().__init__(prefix, name)
20
20
 
21
21
 
22
- class FooController(DetectorControl):
22
+ class FooController(DetectorController):
23
23
  def __init__(self, driver: FooDriver) -> None:
24
24
  self._drv = driver
25
25
 
@@ -0,0 +1,54 @@
1
+ import asyncio
2
+
3
+ import bluesky.plan_stubs as bps
4
+ import bluesky.plans as bp
5
+ from bluesky import RunEngine
6
+
7
+ from ophyd_async.tango.demo import (
8
+ DemoCounter,
9
+ DemoMover,
10
+ TangoDetector,
11
+ )
12
+ from tango.test_context import MultiDeviceTestContext
13
+
14
+ content = (
15
+ {
16
+ "class": DemoMover,
17
+ "devices": [{"name": "demo/motor/1"}],
18
+ },
19
+ {
20
+ "class": DemoCounter,
21
+ "devices": [{"name": "demo/counter/1"}, {"name": "demo/counter/2"}],
22
+ },
23
+ )
24
+
25
+ tango_context = MultiDeviceTestContext(content)
26
+
27
+
28
+ async def main():
29
+ with tango_context:
30
+ detector = TangoDetector(
31
+ trl="",
32
+ name="detector",
33
+ counters_kwargs={"prefix": "demo/counter/", "count": 2},
34
+ mover_kwargs={"trl": "demo/motor/1"},
35
+ )
36
+ await detector.connect()
37
+
38
+ RE = RunEngine()
39
+
40
+ RE(bps.read(detector))
41
+ RE(bps.mv(detector, 0))
42
+ RE(bp.count(list(detector.counters.values())))
43
+
44
+ set_status = detector.set(1.0)
45
+ await asyncio.sleep(0.1)
46
+ stop_status = detector.stop()
47
+ await set_status
48
+ await stop_status
49
+ assert all([set_status.done, stop_status.done])
50
+ assert all([set_status.success, stop_status.success])
51
+
52
+
53
+ if __name__ == "__main__":
54
+ asyncio.run(main())
@@ -31,7 +31,7 @@ There will be a flat public namespace under core, with contents reimported from
31
31
  - `_signal.py` for `Signal`, `SignalBackend`, `observe_signal`, etc.
32
32
  - `_mock.py` for `MockSignalBackend`, `get_mock_put`, etc.
33
33
  - `_readable.py` for `StandardReadable`, `ConfigSignal`, `HintedSignal`, etc.
34
- - `_detector.py` for `StandardDetector`, `DetectorWriter`, `DetectorControl`, `TriggerInfo`, etc.
34
+ - `_detector.py` for `StandardDetector`, `DetectorWriter`, `DetectorController`, `TriggerInfo`, etc.
35
35
  - `_flyer.py` for `StandardFlyer`, `FlyerControl`, etc.
36
36
 
37
37
  There are some renames that will be required, e.g. `HardwareTriggeredFlyable` -> `StandardFlyer`
@@ -10,7 +10,7 @@ Make a StandardDetector
10
10
  The `StandardDetector` is a simple compound device, with 2 standard components:
11
11
 
12
12
  - `DetectorWriter` to handle data persistence, i/o and pass information about data to the RunEngine (usually an instance of :py:class:`ADHDFWriter`)
13
- - `DetectorControl` with logic for arming and disarming the detector. This will be unique to the StandardDetector implementation.
13
+ - `DetectorController` with logic for arming and disarming the detector. This will be unique to the StandardDetector implementation.
14
14
 
15
15
  Writing an AreaDetector StandardDetector
16
16
  ----------------------------------------
@@ -28,9 +28,9 @@ Enumeration fields should be named to prevent namespace collision, i.e. for a Si
28
28
  :language: python
29
29
  :pyobject: FooDriver
30
30
 
31
- Define a :py:class:`FooController` with handling for converting the standard pattern of :py:meth:`ophyd_async.core.DetectorControl.arm` and :py:meth:`ophyd_async.core.DetectorControl.disarm` to required state of :py:class:`FooDriver` e.g. setting a compatible "FooTriggerSource" for a given `DetectorTrigger`, or raising an exception if incompatible with the `DetectorTrigger`.
31
+ Define a :py:class:`FooController` with handling for converting the standard pattern of :py:meth:`ophyd_async.core.DetectorController.arm` and :py:meth:`ophyd_async.core.DetectorController.disarm` to required state of :py:class:`FooDriver` e.g. setting a compatible "FooTriggerSource" for a given `DetectorTrigger`, or raising an exception if incompatible with the `DetectorTrigger`.
32
32
 
33
- The :py:meth:`ophyd_async.core.DetectorControl.get_deadtime` method is used when constructing sequence tables for hardware controlled scanning. Details on how to calculate the deadtime may be only available from technical manuals or otherwise complex. **If it requires fetching from signals, it is recommended to cache the value during the StandardDetector `prepare` method.**
33
+ The :py:meth:`ophyd_async.core.DetectorController.get_deadtime` method is used when constructing sequence tables for hardware controlled scanning. Details on how to calculate the deadtime may be only available from technical manuals or otherwise complex. **If it requires fetching from signals, it is recommended to cache the value during the StandardDetector `prepare` method.**
34
34
 
35
35
  .. literalinclude:: ../examples/foo_detector.py
36
36
  :pyobject: FooController
@@ -47,8 +47,8 @@ If the :py:class:`FooDriver` signals that should be read as configuration, they
47
47
  Writing a non-AreaDetector StandardDetector
48
48
  -------------------------------------------
49
49
 
50
- A non-AreaDetector `StandardDetector` should implement `DetectorControl` and `DetectorWriter` directly.
51
- Here we construct a `DetectorControl` that co-ordinates signals on a PandA PositionCapture block - a child device "pcap" of the `StandardDetector` implementation, analogous to the :py:class:`FooDriver`.
50
+ A non-AreaDetector `StandardDetector` should implement `DetectorController` and `DetectorWriter` directly.
51
+ Here we construct a `DetectorController` that co-ordinates signals on a PandA PositionCapture block - a child device "pcap" of the `StandardDetector` implementation, analogous to the :py:class:`FooDriver`.
52
52
 
53
53
  .. literalinclude:: ../../src/ophyd_async/fastcs/panda/_control.py
54
54
  :pyobject: PandaPcapController
@@ -33,10 +33,12 @@ requires-python = ">=3.10"
33
33
  ca = ["aioca>=1.6"]
34
34
  pva = ["p4p"]
35
35
  sim = ["h5py"]
36
+ tango = ["pytango>=10.0.0"]
36
37
  dev = [
37
38
  "ophyd_async[pva]",
38
39
  "ophyd_async[sim]",
39
40
  "ophyd_async[ca]",
41
+ "ophyd_async[tango]",
40
42
  "black",
41
43
  "flake8",
42
44
  "flake8-isort",
@@ -59,6 +61,7 @@ dev = [
59
61
  "pytest-asyncio",
60
62
  "pytest-cov",
61
63
  "pytest-faulthandler",
64
+ "pytest-forked",
62
65
  "pytest-rerunfailures",
63
66
  "pytest-timeout",
64
67
  "ruff",
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.6.0'
16
- __version_tuple__ = version_tuple = (0, 6, 0)
15
+ __version__ = version = '0.7.0'
16
+ __version_tuple__ = version_tuple = (0, 7, 0)
@@ -1,5 +1,5 @@
1
1
  from ._detector import (
2
- DetectorControl,
2
+ DetectorController,
3
3
  DetectorTrigger,
4
4
  DetectorWriter,
5
5
  StandardDetector,
@@ -16,7 +16,7 @@ from ._device_save_loader import (
16
16
  set_signal_values,
17
17
  walk_rw_signals,
18
18
  )
19
- from ._flyer import StandardFlyer, TriggerLogic
19
+ from ._flyer import FlyerController, StandardFlyer
20
20
  from ._hdf_dataset import HDFDataset, HDFFile
21
21
  from ._log import config_ophyd_async_logging
22
22
  from ._mock_signal_backend import MockSignalBackend
@@ -85,7 +85,7 @@ from ._utils import (
85
85
  )
86
86
 
87
87
  __all__ = [
88
- "DetectorControl",
88
+ "DetectorController",
89
89
  "DetectorTrigger",
90
90
  "DetectorWriter",
91
91
  "StandardDetector",
@@ -102,7 +102,7 @@ __all__ = [
102
102
  "set_signal_values",
103
103
  "walk_rw_signals",
104
104
  "StandardFlyer",
105
- "TriggerLogic",
105
+ "FlyerController",
106
106
  "HDFDataset",
107
107
  "HDFFile",
108
108
  "config_ophyd_async_logging",
@@ -3,8 +3,9 @@
3
3
  import asyncio
4
4
  import time
5
5
  from abc import ABC, abstractmethod
6
- from collections.abc import AsyncGenerator, AsyncIterator, Callable, Sequence
6
+ from collections.abc import AsyncGenerator, AsyncIterator, Callable, Iterator, Sequence
7
7
  from enum import Enum
8
+ from functools import cached_property
8
9
  from typing import (
9
10
  Generic,
10
11
  )
@@ -20,7 +21,7 @@ from bluesky.protocols import (
20
21
  WritesStreamAssets,
21
22
  )
22
23
  from event_model import DataKey
23
- from pydantic import BaseModel, Field
24
+ from pydantic import BaseModel, Field, NonNegativeInt, computed_field
24
25
 
25
26
  from ._device import Device
26
27
  from ._protocol import AsyncConfigurable, AsyncReadable
@@ -45,8 +46,16 @@ class DetectorTrigger(str, Enum):
45
46
  class TriggerInfo(BaseModel):
46
47
  """Minimal set of information required to setup triggering on a detector"""
47
48
 
48
- #: Number of triggers that will be sent, 0 means infinite
49
- number: int = Field(ge=0)
49
+ #: Number of triggers that will be sent, (0 means infinite) Can be:
50
+ # - A single integer or
51
+ # - A list of integers for multiple triggers
52
+ # Example for tomography: TriggerInfo(number=[2,3,100,3])
53
+ #: This would trigger:
54
+ #: - 2 times for dark field images
55
+ #: - 3 times for initial flat field images
56
+ #: - 100 times for projections
57
+ #: - 3 times for final flat field images
58
+ number_of_triggers: NonNegativeInt | list[NonNegativeInt]
50
59
  #: Sort of triggers that will be sent
51
60
  trigger: DetectorTrigger = Field(default=DetectorTrigger.internal)
52
61
  #: What is the minimum deadtime between triggers
@@ -60,13 +69,18 @@ class TriggerInfo(BaseModel):
60
69
  #: e.g. if num=10 and multiplier=5 then the detector will take 10 frames,
61
70
  #: but publish 2 indices, and describe() will show a shape of (5, h, w)
62
71
  multiplier: int = 1
63
- #: The number of times the detector can go through a complete cycle of kickoff and
64
- #: complete without needing to re-arm. This is important for detectors where the
65
- #: process of arming is expensive in terms of time
66
- iteration: int = 1
72
+
73
+ @computed_field
74
+ @cached_property
75
+ def total_number_of_triggers(self) -> int:
76
+ return (
77
+ sum(self.number_of_triggers)
78
+ if isinstance(self.number_of_triggers, list)
79
+ else self.number_of_triggers
80
+ )
67
81
 
68
82
 
69
- class DetectorControl(ABC):
83
+ class DetectorController(ABC):
70
84
  """
71
85
  Classes implementing this interface should hold the logic for
72
86
  arming and disarming a detector
@@ -167,7 +181,7 @@ class StandardDetector(
167
181
 
168
182
  def __init__(
169
183
  self,
170
- controller: DetectorControl,
184
+ controller: DetectorController,
171
185
  writer: DetectorWriter,
172
186
  config_sigs: Sequence[SignalR] = (),
173
187
  name: str = "",
@@ -192,14 +206,18 @@ class StandardDetector(
192
206
  # For kickoff
193
207
  self._watchers: list[Callable] = []
194
208
  self._fly_status: WatchableAsyncStatus | None = None
195
- self._fly_start: float
196
- self._iterations_completed: int = 0
197
- self._intial_frame: int
198
- self._last_frame: int
209
+ self._fly_start: float | None = None
210
+ self._frames_to_complete: int = 0
211
+ # Represents the total number of frames that will have been completed at the
212
+ # end of the next `complete`.
213
+ self._completable_frames: int = 0
214
+ self._number_of_triggers_iter: Iterator[int] | None = None
215
+ self._initial_frame: int = 0
216
+
199
217
  super().__init__(name)
200
218
 
201
219
  @property
202
- def controller(self) -> DetectorControl:
220
+ def controller(self) -> DetectorController:
203
221
  return self._controller
204
222
 
205
223
  @property
@@ -208,7 +226,7 @@ class StandardDetector(
208
226
 
209
227
  @AsyncStatus.wrap
210
228
  async def stage(self) -> None:
211
- # Disarm the detector, stop filewriting.
229
+ # Disarm the detector, stop file writing.
212
230
  await self._check_config_sigs()
213
231
  await asyncio.gather(self.writer.close(), self.controller.disarm())
214
232
  self._trigger_info = None
@@ -251,7 +269,7 @@ class StandardDetector(
251
269
  if self._trigger_info is None:
252
270
  await self.prepare(
253
271
  TriggerInfo(
254
- number=1,
272
+ number_of_triggers=1,
255
273
  trigger=DetectorTrigger.internal,
256
274
  deadtime=None,
257
275
  livetime=None,
@@ -301,8 +319,12 @@ class StandardDetector(
301
319
  f"but trigger logic provides only {value.deadtime}s"
302
320
  )
303
321
  self._trigger_info = value
322
+ self._number_of_triggers_iter = iter(
323
+ self._trigger_info.number_of_triggers
324
+ if isinstance(self._trigger_info.number_of_triggers, list)
325
+ else [self._trigger_info.number_of_triggers]
326
+ )
304
327
  self._initial_frame = await self.writer.get_indices_written()
305
- self._last_frame = self._initial_frame + self._trigger_info.number
306
328
  self._describe, _ = await asyncio.gather(
307
329
  self.writer.open(value.multiplier), self.controller.prepare(value)
308
330
  )
@@ -312,35 +334,50 @@ class StandardDetector(
312
334
 
313
335
  @AsyncStatus.wrap
314
336
  async def kickoff(self):
315
- assert self._trigger_info, "Prepare must be called before kickoff!"
316
- if self._iterations_completed >= self._trigger_info.iteration:
317
- raise Exception(f"Kickoff called more than {self._trigger_info.iteration}")
318
- self._iterations_completed += 1
337
+ if self._trigger_info is None or self._number_of_triggers_iter is None:
338
+ raise RuntimeError("Prepare must be called before kickoff!")
339
+ try:
340
+ self._frames_to_complete = next(self._number_of_triggers_iter)
341
+ self._completable_frames += self._frames_to_complete
342
+ except StopIteration as err:
343
+ raise RuntimeError(
344
+ f"Kickoff called more than the configured number of "
345
+ f"{self._trigger_info.total_number_of_triggers} iteration(s)!"
346
+ ) from err
319
347
 
320
348
  @WatchableAsyncStatus.wrap
321
349
  async def complete(self):
322
350
  assert self._trigger_info
323
- async for index in self.writer.observe_indices_written(
351
+ indices_written = self.writer.observe_indices_written(
324
352
  self._trigger_info.frame_timeout
325
353
  or (
326
354
  DEFAULT_TIMEOUT
327
355
  + (self._trigger_info.livetime or 0)
328
356
  + (self._trigger_info.deadtime or 0)
329
357
  )
330
- ):
331
- yield WatcherUpdate(
332
- name=self.name,
333
- current=index,
334
- initial=self._initial_frame,
335
- target=self._trigger_info.number,
336
- unit="",
337
- precision=0,
338
- time_elapsed=time.monotonic() - self._fly_start,
339
- )
340
- if index >= self._trigger_info.number:
341
- break
342
- if self._iterations_completed == self._trigger_info.iteration:
343
- await self.controller.wait_for_idle()
358
+ )
359
+ try:
360
+ async for index in indices_written:
361
+ yield WatcherUpdate(
362
+ name=self.name,
363
+ current=index,
364
+ initial=self._initial_frame,
365
+ target=self._frames_to_complete,
366
+ unit="",
367
+ precision=0,
368
+ time_elapsed=time.monotonic() - self._fly_start
369
+ if self._fly_start
370
+ else None,
371
+ )
372
+ if index >= self._frames_to_complete:
373
+ break
374
+ finally:
375
+ await indices_written.aclose()
376
+ if self._completable_frames >= self._trigger_info.total_number_of_triggers:
377
+ self._completable_frames = 0
378
+ self._frames_to_complete = 0
379
+ self._number_of_triggers_iter = None
380
+ await self.controller.wait_for_idle()
344
381
 
345
382
  async def describe_collect(self) -> dict[str, DataKey]:
346
383
  return self._describe
@@ -12,7 +12,7 @@ from typing import (
12
12
  )
13
13
 
14
14
  from bluesky.protocols import HasName
15
- from bluesky.run_engine import call_in_bluesky_event_loop
15
+ from bluesky.run_engine import call_in_bluesky_event_loop, in_bluesky_event_loop
16
16
 
17
17
  from ._utils import DEFAULT_TIMEOUT, NotConnected, wait_for_connection
18
18
 
@@ -224,6 +224,11 @@ class DeviceCollector:
224
224
  await self._on_exit()
225
225
 
226
226
  def __exit__(self, type_, value, traceback):
227
+ if in_bluesky_event_loop():
228
+ raise RuntimeError(
229
+ "Cannot use DeviceConnector inside a plan, instead use "
230
+ "`yield from ophyd_async.plan_stubs.ensure_connected(device)`"
231
+ )
227
232
  self._objects_on_exit = self._caller_locals()
228
233
  try:
229
234
  fut = call_in_bluesky_event_loop(self._on_exit())
@@ -1,17 +1,14 @@
1
1
  from abc import ABC, abstractmethod
2
- from collections.abc import Sequence
3
2
  from typing import Generic
4
3
 
5
- from bluesky.protocols import Flyable, Preparable, Reading, Stageable
6
- from event_model import DataKey
4
+ from bluesky.protocols import Flyable, Preparable, Stageable
7
5
 
8
6
  from ._device import Device
9
- from ._signal import SignalR
10
7
  from ._status import AsyncStatus
11
- from ._utils import T, merge_gathered_dicts
8
+ from ._utils import T
12
9
 
13
10
 
14
- class TriggerLogic(ABC, Generic[T]):
11
+ class FlyerController(ABC, Generic[T]):
15
12
  @abstractmethod
16
13
  async def prepare(self, value: T):
17
14
  """Move to the start of the flyscan"""
@@ -38,16 +35,14 @@ class StandardFlyer(
38
35
  ):
39
36
  def __init__(
40
37
  self,
41
- trigger_logic: TriggerLogic[T],
42
- configuration_signals: Sequence[SignalR] = (),
38
+ trigger_logic: FlyerController[T],
43
39
  name: str = "",
44
40
  ):
45
41
  self._trigger_logic = trigger_logic
46
- self._configuration_signals = tuple(configuration_signals)
47
42
  super().__init__(name=name)
48
43
 
49
44
  @property
50
- def trigger_logic(self) -> TriggerLogic[T]:
45
+ def trigger_logic(self) -> FlyerController[T]:
51
46
  return self._trigger_logic
52
47
 
53
48
  @AsyncStatus.wrap
@@ -73,13 +68,3 @@ class StandardFlyer(
73
68
  @AsyncStatus.wrap
74
69
  async def complete(self) -> None:
75
70
  await self._trigger_logic.complete()
76
-
77
- async def describe_configuration(self) -> dict[str, DataKey]:
78
- return await merge_gathered_dicts(
79
- [sig.describe() for sig in self._configuration_signals]
80
- )
81
-
82
- async def read_configuration(self) -> dict[str, Reading]:
83
- return await merge_gathered_dicts(
84
- [sig.read() for sig in self._configuration_signals]
85
- )
@@ -0,0 +1,146 @@
1
+ from enum import Enum
2
+ from typing import TypeVar, get_args, get_origin
3
+
4
+ import numpy as np
5
+ from pydantic import BaseModel, ConfigDict, model_validator
6
+
7
+ TableSubclass = TypeVar("TableSubclass", bound="Table")
8
+
9
+
10
+ def _concat(value1, value2):
11
+ if isinstance(value1, np.ndarray):
12
+ return np.concatenate((value1, value2))
13
+ else:
14
+ return value1 + value2
15
+
16
+
17
+ class Table(BaseModel):
18
+ """An abstraction of a Table of str to numpy array."""
19
+
20
+ model_config = ConfigDict(validate_assignment=True, strict=False)
21
+
22
+ @staticmethod
23
+ def row(cls: type[TableSubclass], **kwargs) -> TableSubclass: # type: ignore
24
+ arrayified_kwargs = {}
25
+ for field_name, field_value in cls.model_fields.items():
26
+ value = kwargs.pop(field_name)
27
+ if field_value.default_factory is None:
28
+ raise ValueError(
29
+ "`Table` models should have default factories for their "
30
+ "mutable empty columns."
31
+ )
32
+ default_array = field_value.default_factory()
33
+ if isinstance(default_array, np.ndarray):
34
+ arrayified_kwargs[field_name] = np.array(
35
+ [value], dtype=default_array.dtype
36
+ )
37
+ elif issubclass(type(value), Enum) and isinstance(value, str):
38
+ arrayified_kwargs[field_name] = [value]
39
+ else:
40
+ raise TypeError(
41
+ "Row column should be numpy arrays or sequence of string `Enum`."
42
+ )
43
+ if kwargs:
44
+ raise TypeError(
45
+ f"Unexpected keyword arguments {kwargs.keys()} for {cls.__name__}."
46
+ )
47
+ return cls(**arrayified_kwargs)
48
+
49
+ def __add__(self, right: TableSubclass) -> TableSubclass:
50
+ """Concatenate the arrays in field values."""
51
+
52
+ if type(right) is not type(self):
53
+ raise RuntimeError(
54
+ f"{right} is not a `Table`, or is not the same "
55
+ f"type of `Table` as {self}."
56
+ )
57
+
58
+ return type(right)(
59
+ **{
60
+ field_name: _concat(
61
+ getattr(self, field_name), getattr(right, field_name)
62
+ )
63
+ for field_name in self.model_fields
64
+ }
65
+ )
66
+
67
+ def numpy_dtype(self) -> np.dtype:
68
+ dtype = []
69
+ for field_name, field_value in self.model_fields.items():
70
+ if np.ndarray in (
71
+ get_origin(field_value.annotation),
72
+ field_value.annotation,
73
+ ):
74
+ dtype.append((field_name, getattr(self, field_name).dtype))
75
+ else:
76
+ enum_type = get_args(field_value.annotation)[0]
77
+ assert issubclass(enum_type, Enum)
78
+ enum_values = [element.value for element in enum_type]
79
+ max_length_in_enum = max(len(value) for value in enum_values)
80
+ dtype.append((field_name, np.dtype(f"<U{max_length_in_enum}")))
81
+
82
+ return np.dtype(dtype)
83
+
84
+ def numpy_table(self):
85
+ # It would be nice to be able to use np.transpose for this,
86
+ # but it defaults to the largest dtype for everything.
87
+ dtype = self.numpy_dtype()
88
+ transposed_list = [
89
+ np.array(tuple(row), dtype=dtype)
90
+ for row in zip(*self.numpy_columns(), strict=False)
91
+ ]
92
+ transposed = np.array(transposed_list, dtype=dtype)
93
+ return transposed
94
+
95
+ def numpy_columns(self) -> list[np.ndarray]:
96
+ """Columns in the table can be lists of string enums or numpy arrays.
97
+
98
+ This method returns the columns, converting the string enums to numpy arrays.
99
+ """
100
+
101
+ columns = []
102
+ for field_name, field_value in self.model_fields.items():
103
+ if np.ndarray in (
104
+ get_origin(field_value.annotation),
105
+ field_value.annotation,
106
+ ):
107
+ columns.append(getattr(self, field_name))
108
+ else:
109
+ enum_type = get_args(field_value.annotation)[0]
110
+ assert issubclass(enum_type, Enum)
111
+ enum_values = [element.value for element in enum_type]
112
+ max_length_in_enum = max(len(value) for value in enum_values)
113
+ dtype = np.dtype(f"<U{max_length_in_enum}")
114
+
115
+ columns.append(
116
+ np.array(
117
+ [enum.value for enum in getattr(self, field_name)], dtype=dtype
118
+ )
119
+ )
120
+
121
+ return columns
122
+
123
+ @model_validator(mode="after")
124
+ def validate_arrays(self) -> "Table":
125
+ first_length = len(next(iter(self))[1])
126
+ assert all(
127
+ len(field_value) == first_length for _, field_value in self
128
+ ), "Rows should all be of equal size."
129
+
130
+ if not all(
131
+ # Checks if the values are numpy subtypes if the array is a numpy array,
132
+ # or if the value is a string enum.
133
+ np.issubdtype(getattr(self, field_name).dtype, default_array.dtype)
134
+ if isinstance(
135
+ default_array := self.model_fields[field_name].default_factory(), # type: ignore
136
+ np.ndarray,
137
+ )
138
+ else issubclass(get_args(field_value.annotation)[0], Enum)
139
+ for field_name, field_value in self.model_fields.items()
140
+ ):
141
+ raise ValueError(
142
+ f"Cannot construct a `{type(self).__name__}`, "
143
+ "some rows have incorrect types."
144
+ )
145
+
146
+ return self
@@ -2,7 +2,7 @@ import asyncio
2
2
  from typing import Literal
3
3
 
4
4
  from ophyd_async.core import (
5
- DetectorControl,
5
+ DetectorController,
6
6
  DetectorTrigger,
7
7
  TriggerInfo,
8
8
  set_and_wait_for_value,
@@ -18,7 +18,7 @@ from ._aravis_io import AravisDriverIO, AravisTriggerMode, AravisTriggerSource
18
18
  _HIGHEST_POSSIBLE_DEADTIME = 1961e-6
19
19
 
20
20
 
21
- class AravisController(DetectorControl):
21
+ class AravisController(DetectorController):
22
22
  GPIO_NUMBER = Literal[1, 2, 3, 4]
23
23
 
24
24
  def __init__(self, driver: AravisDriverIO, gpio_number: GPIO_NUMBER) -> None:
@@ -30,7 +30,7 @@ class AravisController(DetectorControl):
30
30
  return _HIGHEST_POSSIBLE_DEADTIME
31
31
 
32
32
  async def prepare(self, trigger_info: TriggerInfo):
33
- if (num := trigger_info.number) == 0:
33
+ if trigger_info.total_number_of_triggers == 0:
34
34
  image_mode = adcore.ImageMode.continuous
35
35
  else:
36
36
  image_mode = adcore.ImageMode.multiple
@@ -43,7 +43,7 @@ class AravisController(DetectorControl):
43
43
 
44
44
  await asyncio.gather(
45
45
  self._drv.trigger_source.set(trigger_source),
46
- self._drv.num_images.set(num),
46
+ self._drv.num_images.set(trigger_info.total_number_of_triggers),
47
47
  self._drv.image_mode.set(image_mode),
48
48
  )
49
49