dls-dodal 1.36.0__py3-none-any.whl → 1.36.2__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 (44) hide show
  1. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.2.dist-info}/METADATA +33 -33
  2. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.2.dist-info}/RECORD +39 -39
  3. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.2.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/__init__.py +1 -0
  6. dodal/beamlines/adsim.py +75 -0
  7. dodal/beamlines/b01_1.py +16 -31
  8. dodal/beamlines/i22.py +124 -265
  9. dodal/beamlines/i24.py +72 -7
  10. dodal/beamlines/p38.py +16 -1
  11. dodal/beamlines/p99.py +22 -53
  12. dodal/beamlines/training_rig.py +16 -26
  13. dodal/cli.py +54 -8
  14. dodal/common/beamlines/beamline_utils.py +32 -2
  15. dodal/common/beamlines/device_helpers.py +2 -0
  16. dodal/devices/adsim.py +10 -10
  17. dodal/devices/attenuator.py +15 -5
  18. dodal/devices/dcm.py +5 -4
  19. dodal/devices/fast_grid_scan.py +21 -46
  20. dodal/devices/focusing_mirror.py +20 -6
  21. dodal/devices/i24/beam_center.py +12 -0
  22. dodal/devices/i24/focus_mirrors.py +60 -0
  23. dodal/devices/i24/pilatus_metadata.py +44 -0
  24. dodal/devices/linkam3.py +1 -1
  25. dodal/devices/motors.py +14 -10
  26. dodal/devices/oav/oav_detector.py +2 -2
  27. dodal/devices/oav/pin_image_recognition/__init__.py +4 -7
  28. dodal/devices/oav/utils.py +1 -0
  29. dodal/devices/p99/sample_stage.py +12 -16
  30. dodal/devices/pressure_jump_cell.py +299 -0
  31. dodal/devices/robot.py +1 -1
  32. dodal/devices/tetramm.py +1 -1
  33. dodal/devices/undulator.py +4 -1
  34. dodal/devices/undulator_dcm.py +7 -19
  35. dodal/devices/zocalo/zocalo_results.py +7 -7
  36. dodal/utils.py +151 -2
  37. dodal/adsim.py +0 -17
  38. dodal/devices/areadetector/__init__.py +0 -10
  39. dodal/devices/areadetector/adaravis.py +0 -101
  40. dodal/devices/areadetector/adsim.py +0 -47
  41. dodal/devices/areadetector/adutils.py +0 -81
  42. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.2.dist-info}/LICENSE +0 -0
  43. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.2.dist-info}/entry_points.txt +0 -0
  44. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.2.dist-info}/top_level.txt +0 -0
dodal/beamlines/p99.py CHANGED
@@ -1,61 +1,30 @@
1
- from dodal.common.beamlines.beamline_utils import device_instantiation, set_beamline
1
+ from dodal.common.beamlines.beamline_utils import device_factory, set_beamline
2
2
  from dodal.devices.motors import XYZPositioner
3
3
  from dodal.devices.p99.sample_stage import FilterMotor, SampleAngleStage
4
4
  from dodal.log import set_beamline as set_log_beamline
5
- from dodal.utils import get_beamline_name
5
+ from dodal.utils import BeamlinePrefix, get_beamline_name
6
6
 
7
- BL = get_beamline_name("BL99P")
7
+ BL = get_beamline_name("p99")
8
+ PREFIX = BeamlinePrefix(BL)
8
9
  set_log_beamline(BL)
9
10
  set_beamline(BL)
10
11
 
11
12
 
12
- def sample_angle_stage(
13
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
14
- ) -> SampleAngleStage:
15
- """Sample stage for p99"""
16
-
17
- return device_instantiation(
18
- SampleAngleStage,
19
- prefix="-MO-STAGE-01:",
20
- name="sample_angle_stage",
21
- wait=wait_for_connection,
22
- fake=fake_with_ophyd_sim,
23
- )
24
-
25
-
26
- def sample_stage_filer(
27
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
28
- ) -> FilterMotor:
29
- """Sample stage for p99"""
30
-
31
- return device_instantiation(
32
- FilterMotor,
33
- prefix="-MO-STAGE-02:MP:SELECT",
34
- name="sample_stage_filer",
35
- wait=wait_for_connection,
36
- fake=fake_with_ophyd_sim,
37
- )
38
-
39
-
40
- def sample_xyz_stage(
41
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
42
- ) -> XYZPositioner:
43
- return device_instantiation(
44
- FilterMotor,
45
- prefix="-MO-STAGE-02:",
46
- name="sample_xyz_stage",
47
- wait=wait_for_connection,
48
- fake=fake_with_ophyd_sim,
49
- )
50
-
51
-
52
- def sample_lab_xyz_stage(
53
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
54
- ) -> XYZPositioner:
55
- return device_instantiation(
56
- FilterMotor,
57
- prefix="-MO-STAGE-02:LAB:",
58
- name="sample_lab_xyz_stage",
59
- wait=wait_for_connection,
60
- fake=fake_with_ophyd_sim,
61
- )
13
+ @device_factory()
14
+ def angle_stage() -> SampleAngleStage:
15
+ return SampleAngleStage(f"{PREFIX.beamline_prefix}-MO-STAGE-01:")
16
+
17
+
18
+ @device_factory()
19
+ def filter() -> FilterMotor:
20
+ return FilterMotor(f"{PREFIX.beamline_prefix}-MO-STAGE-02:MP:SELECT")
21
+
22
+
23
+ @device_factory()
24
+ def sample_stage() -> XYZPositioner:
25
+ return XYZPositioner(f"{PREFIX.beamline_prefix}-MO-STAGE-02:")
26
+
27
+
28
+ @device_factory()
29
+ def lab_stage() -> XYZPositioner:
30
+ return XYZPositioner(f"{PREFIX.beamline_prefix}-MO-STAGE-02:LAB:")
@@ -3,15 +3,16 @@ from pathlib import Path
3
3
  from ophyd_async.epics.adaravis import AravisDetector
4
4
 
5
5
  from dodal.common.beamlines.beamline_utils import (
6
- device_instantiation,
6
+ device_factory,
7
7
  get_path_provider,
8
8
  set_path_provider,
9
9
  )
10
10
  from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
11
+ from dodal.common.beamlines.device_helpers import HDF5_PREFIX
11
12
  from dodal.common.visit import LocalDirectoryServiceClient, StaticVisitPathProvider
12
13
  from dodal.devices.training_rig.sample_stage import TrainingRigSampleStage
13
14
  from dodal.log import set_beamline as set_log_beamline
14
- from dodal.utils import get_beamline_name
15
+ from dodal.utils import BeamlinePrefix, get_beamline_name
15
16
 
16
17
  #
17
18
  # HTSS Training Rig
@@ -20,11 +21,12 @@ from dodal.utils import get_beamline_name
20
21
  # simple motors, a GigE camera and a PandA.
21
22
  # Since there are multiple rigs whose PVs are identical aside from the prefix,
22
23
  # this module can be used for any rig. It should fill in the prefix automatically
23
- # if the ${BEAMLINE} environment variable is correctly set. It currently defaults
24
- # to p47.
24
+ # if the ${BEAMLINE} environment variable is correctly set, else defaulting
25
+ # to p46, which is known to be in good working order.
25
26
  #
26
27
 
27
- BL = get_beamline_name("p47")
28
+ BL = get_beamline_name("p46")
29
+ PREFIX = BeamlinePrefix(BL)
28
30
  set_log_beamline(BL)
29
31
  set_utils_beamline(BL)
30
32
 
@@ -37,28 +39,16 @@ set_path_provider(
37
39
  )
38
40
 
39
41
 
40
- def sample_stage(
41
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
42
- ) -> TrainingRigSampleStage:
43
- return device_instantiation(
44
- TrainingRigSampleStage,
45
- "sample_stage",
46
- "-MO-MAP-01:STAGE:",
47
- wait_for_connection,
48
- fake_with_ophyd_sim,
49
- )
42
+ @device_factory()
43
+ def sample_stage() -> TrainingRigSampleStage:
44
+ return TrainingRigSampleStage(f"{PREFIX.beamline_prefix}-MO-MAP-01:STAGE:")
50
45
 
51
46
 
52
- def det(
53
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
54
- ) -> AravisDetector:
55
- return device_instantiation(
56
- AravisDetector,
57
- "det",
58
- "-EA-DET-01:",
59
- wait_for_connection,
60
- fake_with_ophyd_sim,
61
- drv_suffix="DET:",
62
- hdf_suffix="HDF5:",
47
+ @device_factory()
48
+ def det() -> AravisDetector:
49
+ return AravisDetector(
50
+ f"{PREFIX.beamline_prefix}-EA-DET-01:",
63
51
  path_provider=get_path_provider(),
52
+ drv_suffix="DET:",
53
+ hdf_suffix=HDF5_PREFIX,
64
54
  )
dodal/cli.py CHANGED
@@ -1,11 +1,13 @@
1
1
  import os
2
+ from collections.abc import Mapping
2
3
 
3
4
  import click
4
5
  from bluesky.run_engine import RunEngine
5
6
  from ophyd_async.core import NotConnected
7
+ from ophyd_async.plan_stubs import ensure_connected
6
8
 
7
9
  from dodal.beamlines import all_beamline_names, module_name_for_beamline
8
- from dodal.utils import make_all_devices
10
+ from dodal.utils import AnyDevice, filter_ophyd_devices, make_all_devices
9
11
 
10
12
  from . import __version__
11
13
 
@@ -50,22 +52,66 @@ def connect(beamline: str, all: bool, sim_backend: bool) -> None:
50
52
 
51
53
  # We need to make a RunEngine to allow ophyd-async devices to connect.
52
54
  # See https://blueskyproject.io/ophyd-async/main/explanations/event-loop-choice.html
53
- RunEngine()
55
+ RE = RunEngine(call_returns_result=True)
54
56
 
55
57
  print(f"Attempting connection to {beamline} (using {full_module_path})")
56
- devices, exceptions = make_all_devices(
58
+
59
+ # Force all devices to be lazy (don't connect to PVs on instantiation) and do
60
+ # connection as an extra step, because the alternatives is handling the fact
61
+ # that only some devices may be lazy.
62
+ devices, instance_exceptions = make_all_devices(
57
63
  full_module_path,
58
64
  include_skipped=all,
59
65
  fake_with_ophyd_sim=sim_backend,
66
+ wait_for_connection=False,
60
67
  )
61
- sim_statement = " (sim mode)" if sim_backend else ""
68
+ devices, connect_exceptions = _connect_devices(RE, devices, sim_backend)
62
69
 
63
- print(f"{len(devices)} devices connected{sim_statement}:")
70
+ # Inform user of successful connections
71
+ _report_successful_devices(devices, sim_backend)
72
+
73
+ # If exceptions have occurred, this will print details of the relevant PVs
74
+ exceptions = {**instance_exceptions, **connect_exceptions}
75
+ if len(exceptions) > 0:
76
+ raise NotConnected(exceptions)
77
+
78
+
79
+ def _report_successful_devices(
80
+ devices: Mapping[str, AnyDevice],
81
+ sim_backend: bool,
82
+ ) -> None:
83
+ sim_statement = " (sim mode)" if sim_backend else ""
64
84
  connected_devices = "\n".join(
65
85
  sorted([f"\t{device_name}" for device_name in devices.keys()])
66
86
  )
87
+
88
+ print(f"{len(devices)} devices connected{sim_statement}:")
67
89
  print(connected_devices)
68
90
 
69
- # If exceptions have occurred, this will print details of the relevant PVs
70
- if len(exceptions) > 0:
71
- raise NotConnected(exceptions)
91
+
92
+ def _connect_devices(
93
+ RE: RunEngine,
94
+ devices: Mapping[str, AnyDevice],
95
+ sim_backend: bool,
96
+ ) -> tuple[Mapping[str, AnyDevice], Mapping[str, Exception]]:
97
+ ophyd_devices, ophyd_async_devices = filter_ophyd_devices(devices)
98
+ exceptions = {}
99
+
100
+ # Connect ophyd devices
101
+ for name, device in ophyd_devices.items():
102
+ try:
103
+ device.wait_for_connection()
104
+ except Exception as ex:
105
+ exceptions[name] = ex
106
+
107
+ # Connect ophyd-async devices
108
+ try:
109
+ RE(ensure_connected(*ophyd_async_devices.values(), mock=sim_backend))
110
+ except NotConnected as ex:
111
+ exceptions = {**exceptions, **ex.sub_errors}
112
+
113
+ # Only return the subset of devices that haven't raised an exception
114
+ successful_devices = {
115
+ name: device for name, device in devices.items() if name not in exceptions
116
+ }
117
+ return successful_devices, exceptions
@@ -1,15 +1,23 @@
1
1
  import inspect
2
2
  from collections.abc import Callable
3
- from typing import Final, TypeVar, cast
3
+ from typing import Annotated, Final, TypeVar, cast
4
4
 
5
5
  from bluesky.run_engine import call_in_bluesky_event_loop
6
6
  from ophyd import Device as OphydV1Device
7
7
  from ophyd.sim import make_fake_device
8
+ from ophyd_async.core import DEFAULT_TIMEOUT
8
9
  from ophyd_async.core import Device as OphydV2Device
9
10
  from ophyd_async.core import wait_for_connection as v2_device_wait_for_connection
10
11
 
11
12
  from dodal.common.types import UpdatingPathProvider
12
- from dodal.utils import AnyDevice, BeamlinePrefix, skip_device
13
+ from dodal.utils import (
14
+ AnyDevice,
15
+ BeamlinePrefix,
16
+ D,
17
+ DeviceInitializationController,
18
+ SkipType,
19
+ skip_device,
20
+ )
13
21
 
14
22
  DEFAULT_CONNECTION_TIMEOUT: Final[float] = 5.0
15
23
 
@@ -124,6 +132,28 @@ def device_instantiation(
124
132
  return device_instance
125
133
 
126
134
 
135
+ def device_factory(
136
+ *,
137
+ use_factory_name: Annotated[bool, "Use factory name as name of device"] = True,
138
+ timeout: Annotated[float, "Timeout for connecting to the device"] = DEFAULT_TIMEOUT,
139
+ mock: Annotated[bool, "Use Signals with mock backends for device"] = False,
140
+ skip: Annotated[
141
+ SkipType,
142
+ "mark the factory to be (conditionally) skipped when beamline is imported by external program",
143
+ ] = False,
144
+ ) -> Callable[[Callable[[], D]], DeviceInitializationController[D]]:
145
+ def decorator(factory: Callable[[], D]) -> DeviceInitializationController[D]:
146
+ return DeviceInitializationController(
147
+ factory,
148
+ use_factory_name,
149
+ timeout,
150
+ mock,
151
+ skip,
152
+ )
153
+
154
+ return decorator
155
+
156
+
127
157
  def set_path_provider(provider: UpdatingPathProvider):
128
158
  global PATH_PROVIDER
129
159
 
@@ -2,6 +2,8 @@ from dodal.common.beamlines.beamline_utils import device_instantiation
2
2
  from dodal.devices.slits import Slits
3
3
  from dodal.utils import skip_device
4
4
 
5
+ HDF5_PREFIX = "HDF5:"
6
+
5
7
 
6
8
  @skip_device()
7
9
  def numbered_slits(
dodal/devices/adsim.py CHANGED
@@ -1,13 +1,13 @@
1
- from ophyd import Component, EpicsMotor, MotorBundle
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.motor import Motor
2
3
 
3
4
 
4
- class SimStage(MotorBundle):
5
- """
6
- ADSIM EPICS motors
7
- """
5
+ class SimStage(StandardReadable):
6
+ """Simulated Sample Stage for use with the containerised simulated beamline
7
+ https://github.com/epics-containers/example-services"""
8
8
 
9
- x = Component(EpicsMotor, "M1")
10
- y = Component(EpicsMotor, "M2")
11
- z = Component(EpicsMotor, "M3")
12
- theta = Component(EpicsMotor, "M4")
13
- load = Component(EpicsMotor, "M5")
9
+ def __init__(self, prefix: str, name: str = "sim"):
10
+ with self.add_children_as_readables():
11
+ self.x = Motor(prefix + "M1")
12
+ self.y = Motor(prefix + "M2")
13
+ super().__init__(name=name)
@@ -14,7 +14,20 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal
14
14
  from dodal.log import LOGGER
15
15
 
16
16
 
17
- class Attenuator(StandardReadable, Movable):
17
+ class ReadOnlyAttenuator(StandardReadable):
18
+ """A read-only attenuator class with a minimum set of PVs for reading.
19
+
20
+ The actual_transmission will return a fractional transmission between 0-1.
21
+ """
22
+
23
+ def __init__(self, prefix: str, name: str = "") -> None:
24
+ with self.add_children_as_readables():
25
+ self.actual_transmission = epics_signal_r(float, prefix + "MATCH")
26
+
27
+ super().__init__(name)
28
+
29
+
30
+ class Attenuator(ReadOnlyAttenuator, Movable):
18
31
  """The attenuator will insert filters into the beam to reduce its transmission.
19
32
 
20
33
  This device should be set with:
@@ -42,10 +55,7 @@ class Attenuator(StandardReadable, Movable):
42
55
  self._use_current_energy = epics_signal_x(prefix + "E2WL:USECURRENTENERGY.PROC")
43
56
  self._change = epics_signal_x(prefix + "FANOUT")
44
57
 
45
- with self.add_children_as_readables():
46
- self.actual_transmission = epics_signal_r(float, prefix + "MATCH")
47
-
48
- super().__init__(name)
58
+ super().__init__(prefix, name)
49
59
 
50
60
  @AsyncStatus.wrap
51
61
  async def set(self, value: float):
dodal/devices/dcm.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import numpy as np
2
- from numpy.typing import NDArray
3
- from ophyd_async.core import StandardReadable, soft_signal_r_and_setter
2
+ from ophyd_async.core import Array1D, StandardReadable, soft_signal_r_and_setter
4
3
  from ophyd_async.epics.core import epics_signal_r
5
4
  from ophyd_async.epics.motor import Motor
6
5
 
@@ -56,8 +55,10 @@ class DCM(StandardReadable):
56
55
  )
57
56
  reflection_array = np.array(cm.reflection)
58
57
  self.crystal_metadata_reflection, _ = soft_signal_r_and_setter(
59
- NDArray[np.uint64],
58
+ Array1D[np.uint64],
60
59
  initial_value=reflection_array,
61
60
  )
62
- self.crystal_metadata_d_spacing = epics_signal_r(float, "DSPACING:RBV")
61
+ self.crystal_metadata_d_spacing = epics_signal_r(
62
+ float, prefix + "DSPACING:RBV"
63
+ )
63
64
  super().__init__(name)
@@ -31,12 +31,12 @@ from dodal.parameters.experiment_parameter_base import AbstractExperimentWithBea
31
31
  @dataclass
32
32
  class GridAxis:
33
33
  start: float
34
- step_size: float
34
+ step_size_mm: float
35
35
  full_steps: int
36
36
 
37
37
  def steps_to_motor_position(self, steps):
38
38
  """Gives the motor position based on steps, where steps are 0 indexed"""
39
- return self.start + self.step_size * steps
39
+ return self.start + self.step_size_mm * steps
40
40
 
41
41
  @property
42
42
  def end(self):
@@ -62,44 +62,29 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
62
62
  x_steps: int = 1
63
63
  y_steps: int = 1
64
64
  z_steps: int = 0
65
- x_step_size: float = 0.1
66
- y_step_size: float = 0.1
67
- z_step_size: float = 0.1
68
- x_start: float = 0.1
69
- y1_start: float = 0.1
70
- y2_start: float = 0.1
71
- z1_start: float = 0.1
72
- z2_start: float = 0.1
65
+ x_step_size_mm: float = 0.1
66
+ y_step_size_mm: float = 0.1
67
+ z_step_size_mm: float = 0.1
68
+ x_start_mm: float = 0.1
69
+ y1_start_mm: float = 0.1
70
+ y2_start_mm: float = 0.1
71
+ z1_start_mm: float = 0.1
72
+ z2_start_mm: float = 0.1
73
73
 
74
74
  # Whether to set the stub offsets after centering
75
75
  set_stub_offsets: bool = False
76
76
 
77
- def get_param_positions(self) -> dict:
78
- return {
79
- "x_steps": self.x_steps,
80
- "y_steps": self.y_steps,
81
- "z_steps": self.z_steps,
82
- "x_step_size": self.x_step_size,
83
- "y_step_size": self.y_step_size,
84
- "z_step_size": self.z_step_size,
85
- "x_start": self.x_start,
86
- "y1_start": self.y1_start,
87
- "y2_start": self.y2_start,
88
- "z1_start": self.z1_start,
89
- "z2_start": self.z2_start,
90
- }
91
-
92
77
  @property
93
78
  def x_axis(self) -> GridAxis:
94
- return GridAxis(self.x_start, self.x_step_size, self.x_steps)
79
+ return GridAxis(self.x_start_mm, self.x_step_size_mm, self.x_steps)
95
80
 
96
81
  @property
97
82
  def y_axis(self) -> GridAxis:
98
- return GridAxis(self.y1_start, self.y_step_size, self.y_steps)
83
+ return GridAxis(self.y1_start_mm, self.y_step_size_mm, self.y_steps)
99
84
 
100
85
  @property
101
86
  def z_axis(self) -> GridAxis:
102
- return GridAxis(self.z2_start, self.z_step_size, self.z_steps)
87
+ return GridAxis(self.z2_start_mm, self.z_step_size_mm, self.z_steps)
103
88
 
104
89
  def get_num_images(self):
105
90
  return self.x_steps * (self.y_steps + self.z_steps)
@@ -140,11 +125,6 @@ class ZebraGridScanParams(GridScanParamsCommon):
140
125
 
141
126
  dwell_time_ms: float = 10
142
127
 
143
- def get_param_positions(self):
144
- param_positions = super().get_param_positions()
145
- param_positions["dwell_time_ms"] = self.dwell_time_ms
146
- return param_positions
147
-
148
128
  @field_validator("dwell_time_ms")
149
129
  @classmethod
150
130
  def non_integer_dwell_time(cls, dwell_time_ms: float) -> float:
@@ -166,11 +146,6 @@ class PandAGridScanParams(GridScanParamsCommon):
166
146
 
167
147
  run_up_distance_mm: float = 0.17
168
148
 
169
- def get_param_positions(self):
170
- param_positions = super().get_param_positions()
171
- param_positions["run_up_distance_mm"] = self.run_up_distance_mm
172
- return param_positions
173
-
174
149
 
175
150
  class MotionProgram(Device):
176
151
  def __init__(self, prefix: str, name: str = "") -> None:
@@ -241,14 +216,14 @@ class FastGridScanCommon(StandardReadable, Flyable, ABC, Generic[ParamType]):
241
216
  "x_steps": self.x_steps,
242
217
  "y_steps": self.y_steps,
243
218
  "z_steps": self.z_steps,
244
- "x_step_size": self.x_step_size,
245
- "y_step_size": self.y_step_size,
246
- "z_step_size": self.z_step_size,
247
- "x_start": self.x_start,
248
- "y1_start": self.y1_start,
249
- "y2_start": self.y2_start,
250
- "z1_start": self.z1_start,
251
- "z2_start": self.z2_start,
219
+ "x_step_size_mm": self.x_step_size,
220
+ "y_step_size_mm": self.y_step_size,
221
+ "z_step_size_mm": self.z_step_size,
222
+ "x_start_mm": self.x_start,
223
+ "y1_start_mm": self.y1_start,
224
+ "y2_start_mm": self.y2_start,
225
+ "z1_start_mm": self.z1_start,
226
+ "z2_start_mm": self.z2_start,
252
227
  }
253
228
  super().__init__(name)
254
229
 
@@ -1,3 +1,5 @@
1
+ from typing import TypedDict
2
+
1
3
  from ophyd_async.core import (
2
4
  AsyncStatus,
3
5
  Device,
@@ -36,6 +38,12 @@ class MirrorStripe(StrictEnum):
36
38
  PLATINUM = "Platinum"
37
39
 
38
40
 
41
+ class MirrorStripeConfiguration(TypedDict):
42
+ stripe: MirrorStripe
43
+ yaw_mrad: float
44
+ lat_mm: float
45
+
46
+
39
47
  class MirrorVoltageDemand(StrictEnum):
40
48
  N_A = "N/A"
41
49
  OK = "OK"
@@ -130,7 +138,12 @@ class FocusingMirror(StandardReadable):
130
138
  """Focusing Mirror"""
131
139
 
132
140
  def __init__(
133
- self, name, prefix, bragg_to_lat_lut_path=None, x_suffix="X", y_suffix="Y"
141
+ self,
142
+ prefix: str,
143
+ name: str = "",
144
+ bragg_to_lat_lut_path: str | None = None,
145
+ x_suffix: str = "X",
146
+ y_suffix: str = "Y",
134
147
  ):
135
148
  self.bragg_to_lat_lookup_table_path = bragg_to_lat_lut_path
136
149
  self.yaw_mrad = Motor(prefix + "YAW")
@@ -161,16 +174,17 @@ class FocusingMirrorWithStripes(FocusingMirror):
161
174
  """A focusing mirror where the stripe material can be changed. This is usually done
162
175
  based on the energy of the beamline."""
163
176
 
164
- def __init__(self, name, prefix, *args, **kwargs):
177
+ def __init__(self, prefix: str, name: str = "", *args, **kwargs):
165
178
  self.stripe = epics_signal_rw(MirrorStripe, prefix + "STRP:DVAL")
166
179
  # apply the current set stripe setting
167
180
  self.apply_stripe = epics_signal_x(prefix + "CHANGE.PROC")
168
181
 
169
- super().__init__(name, prefix, *args, **kwargs)
182
+ super().__init__(prefix, name, *args, **kwargs)
170
183
 
171
- def energy_to_stripe(self, energy_kev) -> MirrorStripe:
184
+ def energy_to_stripe(self, energy_kev) -> MirrorStripeConfiguration:
185
+ """Return the stripe, yaw angle and lateral position for the specified energy"""
172
186
  # In future, this should be configurable per-mirror
173
187
  if energy_kev < 7:
174
- return MirrorStripe.BARE
188
+ return {"stripe": MirrorStripe.BARE, "yaw_mrad": 6.2, "lat_mm": 0.0}
175
189
  else:
176
- return MirrorStripe.RHODIUM
190
+ return {"stripe": MirrorStripe.RHODIUM, "yaw_mrad": 0.0, "lat_mm": 10.0}
@@ -0,0 +1,12 @@
1
+ """A small temporary device to get the beam center positions from \
2
+ eiger or pilatus detector on i24"""
3
+
4
+ from ophyd_async.core import StandardReadable
5
+ from ophyd_async.epics.core import epics_signal_rw
6
+
7
+
8
+ class DetectorBeamCenter(StandardReadable):
9
+ def __init__(self, prefix: str, name: str = "") -> None:
10
+ self.beam_x = epics_signal_rw(float, prefix + "BeamX")
11
+ self.beam_y = epics_signal_rw(float, prefix + "BeamY")
12
+ super().__init__(name)
@@ -0,0 +1,60 @@
1
+ from ophyd_async.core import StandardReadable, StrictEnum
2
+ from ophyd_async.epics.core import epics_signal_rw
3
+
4
+ from dodal.common.signal_utils import create_hardware_backed_soft_signal
5
+
6
+
7
+ class HFocusMode(StrictEnum):
8
+ focus10 = "HMFMfocus10"
9
+ focus20d = "HMFMfocus20d"
10
+ focus30d = "HMFMfocus30d"
11
+ focus50d = "HMFMfocus50d"
12
+ focus1050d = "HMFMfocus1030d"
13
+ focus3010d = "HMFMfocus3010d"
14
+
15
+
16
+ class VFocusMode(StrictEnum):
17
+ focus10 = "VMFMfocus10"
18
+ focus20d = "VMFMfocus20d"
19
+ focus30d = "VMFMfocus30d"
20
+ focus50d = "VMFMfocus50d"
21
+ focus1030d = "VMFMfocus1030d"
22
+ focus3010d = "VMFMfocus3010d"
23
+
24
+
25
+ BEAM_SIZES = {
26
+ "focus10": [7, 7],
27
+ "focus20d": [20, 20],
28
+ "focus30d": [30, 30],
29
+ "focus50d": [50, 50],
30
+ "focus1030d": [10, 30],
31
+ "focus3010d": [30, 10],
32
+ }
33
+
34
+
35
+ class FocusMirrorsMode(StandardReadable):
36
+ """A small device to read the focus mode and work out the beam size."""
37
+
38
+ def __init__(self, prefix: str, name: str = "") -> None:
39
+ self.horizontal = epics_signal_rw(HFocusMode, prefix + "G1:TARGETAPPLY")
40
+ self.vertical = epics_signal_rw(VFocusMode, prefix + "G0:TARGETAPPLY")
41
+
42
+ with self.add_children_as_readables():
43
+ self.beam_size_x = create_hardware_backed_soft_signal(
44
+ int, self._get_beam_size_x, units="um"
45
+ )
46
+ self.beam_size_y = create_hardware_backed_soft_signal(
47
+ int, self._get_beam_size_y, units="um"
48
+ )
49
+
50
+ super().__init__(name)
51
+
52
+ async def _get_beam_size_x(self) -> int:
53
+ h_mode = await self.horizontal.get_value()
54
+ beam_x = BEAM_SIZES[h_mode.removeprefix("HMFM")][0]
55
+ return beam_x
56
+
57
+ async def _get_beam_size_y(self) -> int:
58
+ v_mode = await self.vertical.get_value()
59
+ beam_y = BEAM_SIZES[v_mode.removeprefix("VMFM")][1]
60
+ return beam_y