ophyd-async 0.9.0a1__py3-none-any.whl → 0.10.0a1__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 (157) hide show
  1. ophyd_async/__init__.py +5 -8
  2. ophyd_async/_docs_parser.py +12 -0
  3. ophyd_async/_version.py +9 -4
  4. ophyd_async/core/__init__.py +102 -74
  5. ophyd_async/core/_derived_signal.py +271 -0
  6. ophyd_async/core/_derived_signal_backend.py +300 -0
  7. ophyd_async/core/_detector.py +158 -153
  8. ophyd_async/core/_device.py +143 -115
  9. ophyd_async/core/_device_filler.py +82 -9
  10. ophyd_async/core/_flyer.py +16 -7
  11. ophyd_async/core/_hdf_dataset.py +29 -22
  12. ophyd_async/core/_log.py +14 -23
  13. ophyd_async/core/_mock_signal_backend.py +11 -3
  14. ophyd_async/core/_protocol.py +65 -45
  15. ophyd_async/core/_providers.py +28 -9
  16. ophyd_async/core/_readable.py +74 -58
  17. ophyd_async/core/_settings.py +113 -0
  18. ophyd_async/core/_signal.py +304 -174
  19. ophyd_async/core/_signal_backend.py +60 -14
  20. ophyd_async/core/_soft_signal_backend.py +18 -12
  21. ophyd_async/core/_status.py +72 -24
  22. ophyd_async/core/_table.py +54 -17
  23. ophyd_async/core/_utils.py +101 -52
  24. ophyd_async/core/_yaml_settings.py +66 -0
  25. ophyd_async/epics/__init__.py +1 -0
  26. ophyd_async/epics/adandor/__init__.py +9 -0
  27. ophyd_async/epics/adandor/_andor.py +45 -0
  28. ophyd_async/epics/adandor/_andor_controller.py +51 -0
  29. ophyd_async/epics/adandor/_andor_io.py +34 -0
  30. ophyd_async/epics/adaravis/__init__.py +8 -1
  31. ophyd_async/epics/adaravis/_aravis.py +23 -41
  32. ophyd_async/epics/adaravis/_aravis_controller.py +23 -55
  33. ophyd_async/epics/adaravis/_aravis_io.py +13 -28
  34. ophyd_async/epics/adcore/__init__.py +36 -14
  35. ophyd_async/epics/adcore/_core_detector.py +81 -0
  36. ophyd_async/epics/adcore/_core_io.py +145 -95
  37. ophyd_async/epics/adcore/_core_logic.py +179 -88
  38. ophyd_async/epics/adcore/_core_writer.py +223 -0
  39. ophyd_async/epics/adcore/_hdf_writer.py +51 -92
  40. ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
  41. ophyd_async/epics/adcore/_single_trigger.py +6 -5
  42. ophyd_async/epics/adcore/_tiff_writer.py +26 -0
  43. ophyd_async/epics/adcore/_utils.py +3 -2
  44. ophyd_async/epics/adkinetix/__init__.py +2 -1
  45. ophyd_async/epics/adkinetix/_kinetix.py +32 -27
  46. ophyd_async/epics/adkinetix/_kinetix_controller.py +11 -21
  47. ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
  48. ophyd_async/epics/adpilatus/__init__.py +7 -2
  49. ophyd_async/epics/adpilatus/_pilatus.py +28 -40
  50. ophyd_async/epics/adpilatus/_pilatus_controller.py +25 -22
  51. ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
  52. ophyd_async/epics/adsimdetector/__init__.py +8 -1
  53. ophyd_async/epics/adsimdetector/_sim.py +22 -16
  54. ophyd_async/epics/adsimdetector/_sim_controller.py +9 -43
  55. ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
  56. ophyd_async/epics/advimba/__init__.py +10 -1
  57. ophyd_async/epics/advimba/_vimba.py +26 -25
  58. ophyd_async/epics/advimba/_vimba_controller.py +12 -24
  59. ophyd_async/epics/advimba/_vimba_io.py +23 -28
  60. ophyd_async/epics/core/_aioca.py +66 -30
  61. ophyd_async/epics/core/_epics_connector.py +4 -0
  62. ophyd_async/epics/core/_epics_device.py +2 -0
  63. ophyd_async/epics/core/_p4p.py +50 -18
  64. ophyd_async/epics/core/_pvi_connector.py +65 -8
  65. ophyd_async/epics/core/_signal.py +51 -51
  66. ophyd_async/epics/core/_util.py +5 -5
  67. ophyd_async/epics/demo/__init__.py +11 -49
  68. ophyd_async/epics/demo/__main__.py +31 -0
  69. ophyd_async/epics/demo/_ioc.py +32 -0
  70. ophyd_async/epics/demo/_motor.py +82 -0
  71. ophyd_async/epics/demo/_point_detector.py +42 -0
  72. ophyd_async/epics/demo/_point_detector_channel.py +22 -0
  73. ophyd_async/epics/demo/_stage.py +15 -0
  74. ophyd_async/epics/demo/{mover.db → motor.db} +2 -1
  75. ophyd_async/epics/demo/point_detector.db +59 -0
  76. ophyd_async/epics/demo/point_detector_channel.db +21 -0
  77. ophyd_async/epics/eiger/_eiger.py +1 -3
  78. ophyd_async/epics/eiger/_eiger_controller.py +11 -4
  79. ophyd_async/epics/eiger/_eiger_io.py +2 -0
  80. ophyd_async/epics/eiger/_odin_io.py +1 -2
  81. ophyd_async/epics/motor.py +83 -38
  82. ophyd_async/epics/signal.py +4 -1
  83. ophyd_async/epics/testing/__init__.py +14 -14
  84. ophyd_async/epics/testing/_example_ioc.py +68 -73
  85. ophyd_async/epics/testing/_utils.py +19 -44
  86. ophyd_async/epics/testing/test_records.db +16 -0
  87. ophyd_async/epics/testing/test_records_pva.db +17 -16
  88. ophyd_async/fastcs/__init__.py +1 -0
  89. ophyd_async/fastcs/core.py +6 -0
  90. ophyd_async/fastcs/odin/__init__.py +1 -0
  91. ophyd_async/fastcs/panda/__init__.py +8 -8
  92. ophyd_async/fastcs/panda/_block.py +29 -9
  93. ophyd_async/fastcs/panda/_control.py +12 -2
  94. ophyd_async/fastcs/panda/_hdf_panda.py +5 -1
  95. ophyd_async/fastcs/panda/_table.py +13 -7
  96. ophyd_async/fastcs/panda/_trigger.py +23 -9
  97. ophyd_async/fastcs/panda/_writer.py +27 -30
  98. ophyd_async/plan_stubs/__init__.py +16 -0
  99. ophyd_async/plan_stubs/_ensure_connected.py +12 -17
  100. ophyd_async/plan_stubs/_fly.py +3 -5
  101. ophyd_async/plan_stubs/_nd_attributes.py +9 -5
  102. ophyd_async/plan_stubs/_panda.py +14 -0
  103. ophyd_async/plan_stubs/_settings.py +152 -0
  104. ophyd_async/plan_stubs/_utils.py +3 -0
  105. ophyd_async/plan_stubs/_wait_for_awaitable.py +13 -0
  106. ophyd_async/sim/__init__.py +29 -0
  107. ophyd_async/sim/__main__.py +43 -0
  108. ophyd_async/sim/_blob_detector.py +33 -0
  109. ophyd_async/sim/_blob_detector_controller.py +48 -0
  110. ophyd_async/sim/_blob_detector_writer.py +105 -0
  111. ophyd_async/sim/_mirror_horizontal.py +46 -0
  112. ophyd_async/sim/_mirror_vertical.py +74 -0
  113. ophyd_async/sim/_motor.py +233 -0
  114. ophyd_async/sim/_pattern_generator.py +124 -0
  115. ophyd_async/sim/_point_detector.py +86 -0
  116. ophyd_async/sim/_stage.py +19 -0
  117. ophyd_async/tango/__init__.py +1 -0
  118. ophyd_async/tango/core/__init__.py +6 -1
  119. ophyd_async/tango/core/_base_device.py +41 -33
  120. ophyd_async/tango/core/_converters.py +81 -0
  121. ophyd_async/tango/core/_signal.py +21 -33
  122. ophyd_async/tango/core/_tango_readable.py +2 -19
  123. ophyd_async/tango/core/_tango_transport.py +148 -74
  124. ophyd_async/tango/core/_utils.py +47 -0
  125. ophyd_async/tango/demo/_counter.py +2 -0
  126. ophyd_async/tango/demo/_detector.py +2 -0
  127. ophyd_async/tango/demo/_mover.py +10 -6
  128. ophyd_async/tango/demo/_tango/_servers.py +4 -0
  129. ophyd_async/tango/testing/__init__.py +6 -0
  130. ophyd_async/tango/testing/_one_of_everything.py +200 -0
  131. ophyd_async/testing/__init__.py +48 -7
  132. ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
  133. ophyd_async/testing/_assert.py +200 -96
  134. ophyd_async/testing/_mock_signal_utils.py +59 -73
  135. ophyd_async/testing/_one_of_everything.py +146 -0
  136. ophyd_async/testing/_single_derived.py +87 -0
  137. ophyd_async/testing/_utils.py +3 -0
  138. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/METADATA +25 -26
  139. ophyd_async-0.10.0a1.dist-info/RECORD +149 -0
  140. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/WHEEL +1 -1
  141. ophyd_async/core/_device_save_loader.py +0 -274
  142. ophyd_async/epics/demo/_mover.py +0 -95
  143. ophyd_async/epics/demo/_sensor.py +0 -37
  144. ophyd_async/epics/demo/sensor.db +0 -19
  145. ophyd_async/fastcs/panda/_utils.py +0 -16
  146. ophyd_async/sim/demo/__init__.py +0 -19
  147. ophyd_async/sim/demo/_pattern_detector/__init__.py +0 -13
  148. ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +0 -42
  149. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +0 -62
  150. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_writer.py +0 -41
  151. ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +0 -207
  152. ophyd_async/sim/demo/_sim_motor.py +0 -107
  153. ophyd_async/sim/testing/__init__.py +0 -0
  154. ophyd_async-0.9.0a1.dist-info/RECORD +0 -119
  155. ophyd_async-0.9.0a1.dist-info/entry_points.txt +0 -2
  156. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info/licenses}/LICENSE +0 -0
  157. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/top_level.txt +0 -0
@@ -13,12 +13,12 @@ from ophyd_async.core import (
13
13
 
14
14
 
15
15
  def get_pv_basename_and_field(pv: str) -> tuple[str, str | None]:
16
- """Simple utility function for extracting base pv name without field"""
17
-
16
+ """Split PV into record name and field."""
18
17
  if "." in pv:
19
- return (pv.split(".", -1)[0], pv.split(".", -1)[1])
18
+ record, field = pv.split(".", maxsplit=1)
20
19
  else:
21
- return (pv, None)
20
+ record, field = pv, None
21
+ return (record, field)
22
22
 
23
23
 
24
24
  def get_supported_values(
@@ -47,7 +47,7 @@ def get_supported_values(
47
47
 
48
48
 
49
49
  def format_datatype(datatype: Any) -> str:
50
- if get_origin(datatype) is np.ndarray and get_args(datatype)[0] == tuple[int]:
50
+ if get_origin(datatype) is np.ndarray and get_args(datatype):
51
51
  dtype = get_dtype(datatype)
52
52
  return f"Array1D[np.{dtype.name}]"
53
53
  elif get_origin(datatype) is Sequence:
@@ -1,54 +1,16 @@
1
- """Demo EPICS Devices for the tutorial"""
1
+ """Demo EPICS Devices for the tutorial."""
2
2
 
3
- import atexit
4
- import random
5
- import string
6
- import subprocess
7
- import sys
8
- from pathlib import Path
9
-
10
- from ._mover import Mover, SampleStage
11
- from ._sensor import EnergyMode, Sensor, SensorGroup
3
+ from ._ioc import start_ioc_subprocess
4
+ from ._motor import DemoMotor
5
+ from ._point_detector import DemoPointDetector
6
+ from ._point_detector_channel import DemoPointDetectorChannel, EnergyMode
7
+ from ._stage import DemoStage
12
8
 
13
9
  __all__ = [
14
- "Mover",
15
- "SampleStage",
10
+ "DemoMotor",
11
+ "DemoStage",
16
12
  "EnergyMode",
17
- "Sensor",
18
- "SensorGroup",
13
+ "DemoPointDetectorChannel",
14
+ "DemoPointDetector",
15
+ "start_ioc_subprocess",
19
16
  ]
20
-
21
-
22
- def start_ioc_subprocess() -> str:
23
- """Start an IOC subprocess with EPICS database for sample stage and sensor
24
- with the same pv prefix
25
- """
26
-
27
- pv_prefix = "".join(random.choice(string.ascii_uppercase) for _ in range(12)) + ":"
28
- here = Path(__file__).absolute().parent
29
- args = [sys.executable, "-m", "epicscorelibs.ioc"]
30
-
31
- # Create standalone sensor
32
- args += ["-m", f"P={pv_prefix}"]
33
- args += ["-d", str(here / "sensor.db")]
34
-
35
- # Create sensor group
36
- for suffix in ["1", "2", "3"]:
37
- args += ["-m", f"P={pv_prefix}{suffix}:"]
38
- args += ["-d", str(here / "sensor.db")]
39
-
40
- # Create X and Y motors
41
- for suffix in ["X", "Y"]:
42
- args += ["-m", f"P={pv_prefix}{suffix}:"]
43
- args += ["-d", str(here / "mover.db")]
44
-
45
- # Start IOC
46
- process = subprocess.Popen(
47
- args,
48
- stdin=subprocess.PIPE,
49
- stdout=subprocess.PIPE,
50
- stderr=subprocess.STDOUT,
51
- universal_newlines=True,
52
- )
53
- atexit.register(process.communicate, "exit")
54
- return pv_prefix
@@ -0,0 +1,31 @@
1
+ """Used for tutorial `Implementing Devices`."""
2
+
3
+ # Import bluesky and ophyd
4
+ import bluesky.plan_stubs as bps # noqa: F401
5
+ import bluesky.plans as bp # noqa: F401
6
+ from bluesky.callbacks.best_effort import BestEffortCallback
7
+ from bluesky.run_engine import RunEngine, autoawait_in_bluesky_event_loop
8
+
9
+ from ophyd_async.core import init_devices
10
+ from ophyd_async.epics import demo, testing
11
+
12
+ # Create a run engine and make ipython use it for `await` commands
13
+ RE = RunEngine(call_returns_result=True)
14
+ autoawait_in_bluesky_event_loop()
15
+
16
+ # Add a callback for plotting
17
+ bec = BestEffortCallback()
18
+ RE.subscribe(bec)
19
+
20
+ # Start IOC with demo pvs in subprocess
21
+ prefix = testing.generate_random_pv_prefix()
22
+ ioc = demo.start_ioc_subprocess(prefix, num_channels=3)
23
+
24
+ # All Devices created within this block will be
25
+ # connected and named at the end of the with block
26
+ with init_devices():
27
+ # Create a sample stage with X and Y motors
28
+ stage = demo.DemoStage(f"{prefix}STAGE:")
29
+ # Create a multi channel counter with the same number
30
+ # of counters as the IOC
31
+ pdet = demo.DemoPointDetector(f"{prefix}DET:", num_channels=3)
@@ -0,0 +1,32 @@
1
+ import atexit
2
+ from pathlib import Path
3
+
4
+ from ophyd_async.epics.testing import TestingIOC
5
+
6
+ HERE = Path(__file__).absolute().parent
7
+
8
+
9
+ def start_ioc_subprocess(prefix: str, num_channels: int) -> TestingIOC:
10
+ """Start an IOC subprocess for sample stage and sensor.
11
+
12
+ :param prefix: The prefix for the IOC PVs.
13
+ :param num_channels: The number of point detector channels to create.
14
+ """
15
+ ioc = TestingIOC()
16
+ # Create X and Y motors
17
+ for suffix in ["X", "Y"]:
18
+ ioc.add_database(HERE / "motor.db", P=f"{prefix}STAGE:{suffix}:")
19
+ # Create a multichannel counter with num_counters
20
+ ioc.add_database(HERE / "point_detector.db", P=f"{prefix}DET:")
21
+ for i in range(1, num_channels + 1):
22
+ ioc.add_database(
23
+ HERE / "point_detector_channel.db",
24
+ P=f"{prefix}DET:",
25
+ CHANNEL=str(i),
26
+ X=f"{prefix}STAGE:X:",
27
+ Y=f"{prefix}STAGE:Y:",
28
+ )
29
+ # Start IOC and register it to be stopped at exit
30
+ ioc.start()
31
+ atexit.register(ioc.stop)
32
+ return ioc
@@ -0,0 +1,82 @@
1
+ import asyncio
2
+ from typing import Annotated as A
3
+
4
+ import numpy as np
5
+ from bluesky.protocols import Movable, Stoppable
6
+
7
+ from ophyd_async.core import (
8
+ CALCULATE_TIMEOUT,
9
+ DEFAULT_TIMEOUT,
10
+ CalculatableTimeout,
11
+ SignalR,
12
+ SignalRW,
13
+ SignalX,
14
+ StandardReadable,
15
+ WatchableAsyncStatus,
16
+ WatcherUpdate,
17
+ observe_value,
18
+ )
19
+ from ophyd_async.core import StandardReadableFormat as Format
20
+ from ophyd_async.epics.core import EpicsDevice, PvSuffix
21
+
22
+
23
+ class DemoMotor(EpicsDevice, StandardReadable, Movable, Stoppable):
24
+ """A demo movable that moves based on velocity."""
25
+
26
+ # Whether set() should complete successfully or not
27
+ _set_success = True
28
+ # Define some signals
29
+ readback: A[SignalR[float], PvSuffix("Readback"), Format.HINTED_SIGNAL]
30
+ velocity: A[SignalRW[float], PvSuffix("Velocity"), Format.CONFIG_SIGNAL]
31
+ units: A[SignalR[str], PvSuffix("Readback.EGU"), Format.CONFIG_SIGNAL]
32
+ setpoint: A[SignalRW[float], PvSuffix("Setpoint")]
33
+ precision: A[SignalR[int], PvSuffix("Readback.PREC")]
34
+ # If a signal name clashes with a bluesky verb add _ to the attribute name
35
+ stop_: A[SignalX, PvSuffix("Stop.PROC")]
36
+
37
+ def set_name(self, name: str, *, child_name_separator: str | None = None) -> None:
38
+ super().set_name(name, child_name_separator=child_name_separator)
39
+ # Readback should be named the same as its parent in read()
40
+ self.readback.set_name(name)
41
+
42
+ @WatchableAsyncStatus.wrap
43
+ async def set( # type: ignore
44
+ self, new_position: float, timeout: CalculatableTimeout = CALCULATE_TIMEOUT
45
+ ):
46
+ # The move should complete successfully unless stop(success=False) is called
47
+ self._set_success = True
48
+ # Get some variables for the progress bar reporting
49
+ old_position, units, precision, velocity = await asyncio.gather(
50
+ self.setpoint.get_value(),
51
+ self.units.get_value(),
52
+ self.precision.get_value(),
53
+ self.velocity.get_value(),
54
+ )
55
+ # If not supplied, calculate a suitable timeout for the move
56
+ if timeout == CALCULATE_TIMEOUT:
57
+ timeout = abs(new_position - old_position) / velocity + DEFAULT_TIMEOUT
58
+ # Wait for the value to set, but don't wait for put completion callback
59
+ await self.setpoint.set(new_position, wait=False)
60
+ # Observe the readback Signal, and on each new position...
61
+ async for current_position in observe_value(
62
+ self.readback, done_timeout=timeout
63
+ ):
64
+ # Emit a progress bar update
65
+ yield WatcherUpdate(
66
+ current=current_position,
67
+ initial=old_position,
68
+ target=new_position,
69
+ name=self.name,
70
+ unit=units,
71
+ precision=precision,
72
+ )
73
+ # If we are at the desired position the break
74
+ if np.isclose(current_position, new_position):
75
+ break
76
+ # If we were told to stop and report an error then do so
77
+ if not self._set_success:
78
+ raise RuntimeError("Motor was stopped")
79
+
80
+ async def stop(self, success=True):
81
+ self._set_success = success
82
+ await self.stop_.trigger()
@@ -0,0 +1,42 @@
1
+ from typing import Annotated as A
2
+
3
+ from bluesky.protocols import Triggerable
4
+
5
+ from ophyd_async.core import (
6
+ DEFAULT_TIMEOUT,
7
+ AsyncStatus,
8
+ DeviceVector,
9
+ SignalR,
10
+ SignalRW,
11
+ SignalX,
12
+ StandardReadable,
13
+ )
14
+ from ophyd_async.core import StandardReadableFormat as Format
15
+ from ophyd_async.epics.core import EpicsDevice, PvSuffix
16
+
17
+ from ._point_detector_channel import DemoPointDetectorChannel
18
+
19
+
20
+ class DemoPointDetector(StandardReadable, EpicsDevice, Triggerable):
21
+ """A demo detector that produces a point values based on X and Y motors."""
22
+
23
+ acquire_time: A[SignalRW[float], PvSuffix("AcquireTime"), Format.CONFIG_SIGNAL]
24
+ start: A[SignalX, PvSuffix("Start.PROC")]
25
+ acquiring: A[SignalR[bool], PvSuffix("Acquiring")]
26
+ reset: A[SignalX, PvSuffix("Reset.PROC")]
27
+
28
+ def __init__(self, prefix: str, num_channels: int = 3, name: str = "") -> None:
29
+ with self.add_children_as_readables():
30
+ self.channel = DeviceVector(
31
+ {
32
+ i: DemoPointDetectorChannel(f"{prefix}{i}:")
33
+ for i in range(1, num_channels + 1)
34
+ }
35
+ )
36
+ super().__init__(prefix=prefix, name=name)
37
+
38
+ @AsyncStatus.wrap
39
+ async def trigger(self):
40
+ await self.reset.trigger()
41
+ timeout = await self.acquire_time.get_value() + DEFAULT_TIMEOUT
42
+ await self.start.trigger(timeout=timeout)
@@ -0,0 +1,22 @@
1
+ from typing import Annotated as A
2
+
3
+ from ophyd_async.core import SignalR, SignalRW, StandardReadable, StrictEnum
4
+ from ophyd_async.core import StandardReadableFormat as Format
5
+ from ophyd_async.epics.core import EpicsDevice, PvSuffix
6
+
7
+
8
+ class EnergyMode(StrictEnum):
9
+ """Energy mode for `DemoPointDetectorChannel`."""
10
+
11
+ LOW = "Low Energy"
12
+ """Low energy mode"""
13
+
14
+ HIGH = "High Energy"
15
+ """High energy mode"""
16
+
17
+
18
+ class DemoPointDetectorChannel(StandardReadable, EpicsDevice):
19
+ """A channel for `DemoPointDetector` with int value based on X and Y Motors."""
20
+
21
+ value: A[SignalR[int], PvSuffix("Value"), Format.HINTED_UNCACHED_SIGNAL]
22
+ mode: A[SignalRW[EnergyMode], PvSuffix("Mode"), Format.CONFIG_SIGNAL]
@@ -0,0 +1,15 @@
1
+ from ophyd_async.core import StandardReadable
2
+
3
+ from ._motor import DemoMotor
4
+
5
+
6
+ class DemoStage(StandardReadable):
7
+ """A simulated sample stage with X and Y movables."""
8
+
9
+ def __init__(self, prefix: str, name="") -> None:
10
+ # Define some child Devices
11
+ with self.add_children_as_readables():
12
+ self.x = DemoMotor(prefix + "X:")
13
+ self.y = DemoMotor(prefix + "Y:")
14
+ # Set name of device and child devices
15
+ super().__init__(name=name)
@@ -10,7 +10,8 @@ record(ao, "$(P)Velocity") {
10
10
  field(PREC, "$(PREC=3)")
11
11
  field(PINI, "YES")
12
12
  field(EGU, "$(EGU=mm)/s")
13
- field(VAL, "$(VELO=100)")
13
+ field(VAL, "$(VELO=1)")
14
+ field(DRVL, "0")
14
15
  }
15
16
 
16
17
  record(calc, "$(P)VelocityDiv") {
@@ -0,0 +1,59 @@
1
+ record(ao, "$(P)AcquireTime") {
2
+ field(DESC, "Time to acquire for")
3
+ field(VAL, "0.1")
4
+ field(OUT, "$(P)Start.DLY2")
5
+ field(PINI, "YES")
6
+ }
7
+
8
+ record(seq, "$(P)Start") {
9
+ field(DESC, "Start sequence")
10
+ # Grab the start time
11
+ field(LNK0, "$(P)StartTime.PROC")
12
+ # Set it to be acquiring
13
+ field(LNK1, "$(P)Acquiring PP")
14
+ field(DO1, "1")
15
+ # Set it back to idle
16
+ field(LNK2, "$(P)Acquiring PP")
17
+ field(DO2, "0")
18
+ # Set the elapsed time to the full acquire time
19
+ field(LNK3, "$(P)Elapsed PP")
20
+ field(DOL3, "$(P)AcquireTime")
21
+ }
22
+
23
+ record(ai, "$(P)StartTime") {
24
+ field(DTYP, "Soft Timestamp")
25
+ }
26
+
27
+ record(bi, "$(P)Acquiring") {
28
+ field(DESC, "Currently acquiring")
29
+ field(ZNAM, "Idle")
30
+ field(ONAM, "Acquiring")
31
+ field(PINI, "YES")
32
+ }
33
+
34
+ record(ai, "$(P)CurrentTime") {
35
+ field(DTYP, "Soft Timestamp")
36
+ }
37
+
38
+ record(calcout, "$(P)Process") {
39
+ field(DESC, "Process elapsed time if acquiring")
40
+ field(INPA, "$(P)StartTime")
41
+ field(INPB, "$(P)CurrentTime PP")
42
+ field(SCAN, ".1 second")
43
+ field(CALC, "B-A")
44
+ field(OUT, "$(P)Elapsed PP")
45
+ field(SDIS, "$(P)Acquiring")
46
+ field(DISV, "0")
47
+ }
48
+
49
+ record(ai, "$(P)Elapsed") {
50
+ field(DESC, "Elapsed time")
51
+ field(EGU, "s")
52
+ field(PREC, "1")
53
+ field(PINI, "YES")
54
+ }
55
+
56
+ record(calcout, "$(P)Reset") {
57
+ field(OUT, "$(P)Elapsed PP")
58
+ field(CALC, "0")
59
+ }
@@ -0,0 +1,21 @@
1
+ record(mbbo, "$(P)$(CHANNEL):Mode") {
2
+ field(DESC, "Energy sensitivity of the image")
3
+ field(DTYP, "Raw Soft Channel")
4
+ field(PINI, "YES")
5
+ field(ZRVL, "10")
6
+ field(ZRST, "Low Energy")
7
+ field(ONVL, "100")
8
+ field(ONST, "High Energy")
9
+ }
10
+
11
+ record(calc, "$(P)$(CHANNEL):Value") {
12
+ field(DESC, "Sensor value simulated from X and Y")
13
+ field(INPA, "$(X)Readback")
14
+ field(INPB, "$(Y)Readback")
15
+ field(INPC, "$(CHANNEL)")
16
+ field(INPD, "$(P)$(CHANNEL):Mode.RVAL")
17
+ field(INPE, "$(P)Elapsed CP")
18
+ field(CALC, "FLOOR((SIN(A)**C+COS(A*B+D)+2)*2500*E)")
19
+ field(EGU, "cts")
20
+ field(PREC, "0")
21
+ }
@@ -12,9 +12,7 @@ class EigerTriggerInfo(TriggerInfo):
12
12
 
13
13
 
14
14
  class EigerDetector(StandardDetector):
15
- """
16
- Ophyd-async implementation of an Eiger Detector.
17
- """
15
+ """Ophyd-async implementation of an Eiger Detector."""
18
16
 
19
17
  _controller: EigerController
20
18
  _writer: Odin
@@ -2,6 +2,7 @@ import asyncio
2
2
 
3
3
  from ophyd_async.core import (
4
4
  DEFAULT_TIMEOUT,
5
+ AsyncStatus,
5
6
  DetectorController,
6
7
  DetectorTrigger,
7
8
  TriggerInfo,
@@ -19,19 +20,24 @@ EIGER_TRIGGER_MODE_MAP = {
19
20
 
20
21
 
21
22
  class EigerController(DetectorController):
23
+ """Controller for the Eiger detector."""
24
+
22
25
  def __init__(
23
26
  self,
24
27
  driver: EigerDriverIO,
25
28
  ) -> None:
26
29
  self._drv = driver
30
+ self._arm_status: AsyncStatus | None = None
27
31
 
28
32
  def get_deadtime(self, exposure: float | None) -> float:
29
33
  # See https://media.dectris.com/filer_public/30/14/3014704e-5f3b-43ba-8ccf-8ef720e60d2a/240202_usermanual_eiger2.pdf
30
34
  return 0.0001
31
35
 
32
36
  async def set_energy(self, energy: float, tolerance: float = 0.1):
33
- """Changing photon energy takes some time so only do so if the current energy is
34
- outside the tolerance."""
37
+ """Change photon energy if outside tolerance.
38
+
39
+ It takes some time so don't do it unless it is outside tolerance.
40
+ """
35
41
  current_energy = await self._drv.photon_energy.get_value()
36
42
  if abs(current_energy - energy) > tolerance:
37
43
  await self._drv.photon_energy.set(energy)
@@ -54,7 +60,7 @@ class EigerController(DetectorController):
54
60
 
55
61
  async def arm(self):
56
62
  # TODO: Detector state should be an enum see https://github.com/DiamondLightSource/eiger-fastcs/issues/43
57
- self._arm_status = set_and_wait_for_other_value(
63
+ self._arm_status = await set_and_wait_for_other_value(
58
64
  self._drv.arm,
59
65
  1,
60
66
  self._drv.state,
@@ -64,8 +70,9 @@ class EigerController(DetectorController):
64
70
  )
65
71
 
66
72
  async def wait_for_idle(self):
67
- if self._arm_status:
73
+ if self._arm_status and not self._arm_status.done:
68
74
  await self._arm_status
75
+ self._arm_status = None
69
76
 
70
77
  async def disarm(self):
71
78
  await self._drv.disarm.set(1)
@@ -9,6 +9,8 @@ class EigerTriggerMode(StrictEnum):
9
9
 
10
10
 
11
11
  class EigerDriverIO(Device):
12
+ """Contains signals for handling IO on the Eiger detector."""
13
+
12
14
  def __init__(self, prefix: str, name: str = "") -> None:
13
15
  self.bit_depth = epics_signal_r(int, f"{prefix}BitDepthReadout")
14
16
  self.stale_parameters = epics_signal_r(bool, f"{prefix}StaleParameters")
@@ -5,7 +5,6 @@ from bluesky.protocols import StreamAsset
5
5
  from event_model import DataKey
6
6
 
7
7
  from ophyd_async.core import (
8
- DEFAULT_TIMEOUT,
9
8
  DetectorWriter,
10
9
  Device,
11
10
  DeviceVector,
@@ -110,7 +109,7 @@ class OdinWriter(DetectorWriter):
110
109
  }
111
110
 
112
111
  async def observe_indices_written(
113
- self, timeout=DEFAULT_TIMEOUT
112
+ self, timeout: float
114
113
  ) -> AsyncGenerator[int, None]:
115
114
  async for num_captured in observe_value(self._drv.num_captured, timeout):
116
115
  yield num_captured