dls-dodal 1.36.3__py3-none-any.whl → 1.38.0__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 (58) hide show
  1. {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/METADATA +4 -3
  2. {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/RECORD +58 -38
  3. {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/i02_1.py +37 -0
  6. dodal/beamlines/i03.py +34 -5
  7. dodal/beamlines/i04.py +16 -5
  8. dodal/beamlines/i10.py +105 -0
  9. dodal/beamlines/i13_1.py +20 -2
  10. dodal/beamlines/i22.py +15 -0
  11. dodal/beamlines/i24.py +14 -2
  12. dodal/beamlines/p99.py +6 -2
  13. dodal/beamlines/training_rig.py +10 -1
  14. dodal/common/crystal_metadata.py +3 -3
  15. dodal/common/udc_directory_provider.py +3 -1
  16. dodal/devices/aperturescatterguard.py +3 -0
  17. dodal/devices/{attenuator.py → attenuator/attenuator.py} +29 -1
  18. dodal/devices/attenuator/filter.py +11 -0
  19. dodal/devices/attenuator/filter_selections.py +72 -0
  20. dodal/devices/bimorph_mirror.py +151 -0
  21. dodal/devices/current_amplifiers/__init__.py +34 -0
  22. dodal/devices/current_amplifiers/current_amplifier.py +103 -0
  23. dodal/devices/current_amplifiers/current_amplifier_detector.py +109 -0
  24. dodal/devices/current_amplifiers/femto.py +143 -0
  25. dodal/devices/current_amplifiers/sr570.py +214 -0
  26. dodal/devices/current_amplifiers/struck_scaler_counter.py +79 -0
  27. dodal/devices/detector/det_dim_constants.py +15 -0
  28. dodal/devices/eiger_odin.py +3 -3
  29. dodal/devices/fast_grid_scan.py +8 -3
  30. dodal/devices/flux.py +10 -3
  31. dodal/devices/i03/beamstop.py +85 -0
  32. dodal/devices/i04/transfocator.py +67 -53
  33. dodal/devices/i10/rasor/rasor_current_amp.py +72 -0
  34. dodal/devices/i10/rasor/rasor_motors.py +62 -0
  35. dodal/devices/i10/rasor/rasor_scaler_cards.py +12 -0
  36. dodal/devices/i13_1/__init__.py +0 -0
  37. dodal/devices/i13_1/merlin.py +33 -0
  38. dodal/devices/i13_1/merlin_controller.py +52 -0
  39. dodal/devices/i13_1/merlin_io.py +17 -0
  40. dodal/devices/i24/beam_center.py +1 -1
  41. dodal/devices/p45.py +31 -20
  42. dodal/devices/p99/sample_stage.py +2 -28
  43. dodal/devices/robot.py +2 -2
  44. dodal/devices/s4_slit_gaps.py +8 -4
  45. dodal/devices/undulator_dcm.py +9 -11
  46. dodal/devices/util/lookup_tables.py +14 -10
  47. dodal/devices/zebra/__init__.py +0 -0
  48. dodal/devices/{zebra.py → zebra/zebra.py} +9 -33
  49. dodal/devices/zebra/zebra_constants_mapping.py +96 -0
  50. dodal/devices/zocalo/zocalo_interaction.py +2 -1
  51. dodal/devices/zocalo/zocalo_results.py +22 -2
  52. dodal/log.py +2 -2
  53. dodal/plans/wrapped.py +3 -3
  54. {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/LICENSE +0 -0
  55. {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/entry_points.txt +0 -0
  56. {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/top_level.txt +0 -0
  57. /dodal/devices/{zebra_controlled_shutter.py → zebra/zebra_controlled_shutter.py} +0 -0
  58. /dodal/{devices/util → plans}/save_panda.py +0 -0
@@ -1,14 +1,18 @@
1
+ import asyncio
1
2
  import math
2
- from time import sleep, time
3
3
 
4
- from ophyd import Component as Cpt
5
- from ophyd import Device, EpicsSignal, EpicsSignalRO, Kind
6
- from ophyd.status import DeviceStatus
4
+ from ophyd_async.core import (
5
+ AsyncStatus,
6
+ StandardReadable,
7
+ observe_value,
8
+ wait_for_value,
9
+ )
10
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
7
11
 
8
12
  from dodal.log import LOGGER
9
13
 
10
14
 
11
- class Transfocator(Device):
15
+ class Transfocator(StandardReadable):
12
16
  """The transfocator is a device that puts a number of lenses in the beam to change
13
17
  its shape.
14
18
 
@@ -18,34 +22,50 @@ class Transfocator(Device):
18
22
  my_transfocator.set(vert_beamsize_microns)
19
23
  """
20
24
 
21
- beamsize_set_microns = Cpt(EpicsSignal, "VERT_REQ", kind=Kind.hinted)
22
- predicted_vertical_num_lenses = Cpt(EpicsSignal, "LENS_PRED")
23
-
24
- number_filters_sp = Cpt(EpicsSignal, "NUM_FILTERS")
25
-
26
- start = Cpt(EpicsSignal, "START.PROC")
27
- start_rbv = Cpt(EpicsSignalRO, "START_RBV")
28
-
29
- vertical_lens_rbv = Cpt(EpicsSignalRO, "VER", kind=Kind.hinted)
25
+ def __init__(self, prefix: str, name: str = ""):
26
+ with self.add_children_as_readables():
27
+ self.beamsize_set_microns = epics_signal_rw(float, prefix + "VERT_REQ")
28
+ self.predicted_vertical_num_lenses = epics_signal_rw(
29
+ float, prefix + "LENS_PRED"
30
+ )
31
+ self.number_filters_sp = epics_signal_rw(int, prefix + "NUM_FILTERS")
32
+ self.start = epics_signal_rw(int, prefix + "START.PROC")
33
+ self.start_rbv = epics_signal_r(int, prefix + "START_RBV")
34
+ self.vertical_lens_rbv = epics_signal_r(float, prefix + "VER")
30
35
 
31
- TIMEOUT = 120
32
- _POLLING_WAIT = 0.01
36
+ self.TIMEOUT = 120
33
37
 
34
- def polling_wait_on_start_rbv(self, for_value):
35
- # For some reason couldn't get monitors working on START_RBV
36
- # (See https://github.com/DiamondLightSource/dodal/issues/152)
37
- start_time = time()
38
- while time() < start_time + self.TIMEOUT:
39
- RBV_value = self.start_rbv.get()
40
- if RBV_value == for_value:
41
- return
42
- sleep(self._POLLING_WAIT)
38
+ super().__init__(name=name)
43
39
 
44
- # last try
45
- if self.start_rbv.get() != for_value:
46
- raise TimeoutError()
40
+ async def _observe_beamsize_microns(self):
41
+ is_set_filters_done = False
47
42
 
48
- def set(self, beamsize_microns: float) -> DeviceStatus:
43
+ async def set_based_on_prediction(value: float):
44
+ if not math.isclose(
45
+ self.latest_pred_vertical_num_lenses, value, abs_tol=1e-8
46
+ ):
47
+ # We can only put an integer number of lenses in the beam but the
48
+ # calculation in the IOC returns the theoretical float number of lenses
49
+ nonlocal is_set_filters_done
50
+ value = round(value)
51
+ LOGGER.info(f"Transfocator setting {value} filters")
52
+ await self.number_filters_sp.set(value)
53
+ await self.start.set(1)
54
+ LOGGER.info("Waiting for start_rbv to change to 1")
55
+ await wait_for_value(self.start_rbv, 1, self.TIMEOUT)
56
+ LOGGER.info("Waiting for start_rbv to change to 0")
57
+ await wait_for_value(self.start_rbv, 0, self.TIMEOUT)
58
+ self.latest_pred_vertical_num_lenses = value
59
+ is_set_filters_done = True
60
+
61
+ # The value hasn't changed so assume the device is already set up correctly
62
+ async for value in observe_value(self.predicted_vertical_num_lenses):
63
+ await set_based_on_prediction(value)
64
+ if is_set_filters_done:
65
+ break
66
+
67
+ @AsyncStatus.wrap
68
+ async def set(self, value: float):
49
69
  """To set the beamsize on the transfocator we must:
50
70
  1. Set the beamsize in the calculator part of the transfocator
51
71
  2. Get the predicted number of lenses needed from this calculator
@@ -53,30 +73,24 @@ class Transfocator(Device):
53
73
  4. Start the device moving
54
74
  5. Wait for the start_rbv goes high and low again
55
75
  """
56
- subscriber: int
57
- status = DeviceStatus(self, timeout=self.TIMEOUT)
76
+ self.latest_pred_vertical_num_lenses = (
77
+ await self.predicted_vertical_num_lenses.get_value()
78
+ )
58
79
 
59
- def set_based_on_predicition(old_value, value, *args, **kwargs):
60
- if not math.isclose(old_value, value, abs_tol=1e-8):
61
- self.predicted_vertical_num_lenses.unsubscribe(subscriber)
80
+ LOGGER.info(f"Transfocator setting {value} beamsize")
62
81
 
63
- # We can only put an integer number of lenses in the beam but the
64
- # calculation in the IOC returns the theoretical float number of lenses
65
- value = round(value)
66
- LOGGER.info(f"Transfocator setting {value} filters")
67
- self.number_filters_sp.set(value).wait()
68
- self.start.set(1).wait()
69
- self.polling_wait_on_start_rbv(1)
70
- self.polling_wait_on_start_rbv(0)
71
- # The value hasn't changed so assume the device is already set up correctly
72
- status.set_finished()
73
-
74
- LOGGER.info(f"Transfocator setting {beamsize_microns} beamsize")
75
- if self.beamsize_set_microns.get() != beamsize_microns:
76
- subscriber = self.predicted_vertical_num_lenses.subscribe(
77
- set_based_on_predicition, run=False
82
+ if await self.beamsize_set_microns.get_value() != value:
83
+ # Logic in the IOC calculates predicted_vertical_num_lenses when beam_set_microns changes
84
+ await asyncio.gather(
85
+ self.beamsize_set_microns.set(value),
86
+ self._observe_beamsize_microns(),
78
87
  )
79
- self.beamsize_set_microns.set(beamsize_microns)
80
- else:
81
- status.set_finished()
82
- return status
88
+
89
+ number_filters_rbv, vertical_lens_size_rbv = await asyncio.gather(
90
+ self.number_filters_sp.get_value(),
91
+ self.vertical_lens_rbv.get_value(),
92
+ )
93
+
94
+ LOGGER.info(
95
+ f"Transfocator set complete. Number of filters is: {number_filters_rbv} and Vertical beam size is: {vertical_lens_size_rbv}"
96
+ )
@@ -0,0 +1,72 @@
1
+ from ophyd_async.core import Device
2
+
3
+ from dodal.devices.current_amplifiers import (
4
+ SR570,
5
+ Femto3xxGainTable,
6
+ Femto3xxGainToCurrentTable,
7
+ Femto3xxRaiseTime,
8
+ FemtoDDPCA,
9
+ SR570FineGainTable,
10
+ SR570FullGainTable,
11
+ SR570GainTable,
12
+ SR570GainToCurrentTable,
13
+ SR570RaiseTimeTable,
14
+ )
15
+
16
+
17
+ class RasorFemto(Device):
18
+ def __init__(self, prefix: str, suffix: str = "GAIN", name: str = "") -> None:
19
+ self.ca1 = FemtoDDPCA(
20
+ prefix + "-01:",
21
+ suffix=suffix,
22
+ gain_table=Femto3xxGainTable,
23
+ gain_to_current_table=Femto3xxGainToCurrentTable,
24
+ raise_timetable=Femto3xxRaiseTime,
25
+ )
26
+ self.ca2 = FemtoDDPCA(
27
+ prefix + "-02:",
28
+ suffix=suffix,
29
+ gain_table=Femto3xxGainTable,
30
+ gain_to_current_table=Femto3xxGainToCurrentTable,
31
+ raise_timetable=Femto3xxRaiseTime,
32
+ )
33
+ self.ca3 = FemtoDDPCA(
34
+ prefix + "-03:",
35
+ suffix=suffix,
36
+ gain_table=Femto3xxGainTable,
37
+ gain_to_current_table=Femto3xxGainToCurrentTable,
38
+ raise_timetable=Femto3xxRaiseTime,
39
+ )
40
+ super().__init__(name)
41
+
42
+
43
+ class RasorSR570(Device):
44
+ def __init__(self, prefix: str, suffix: str = "SENS:SEL", name: str = "") -> None:
45
+ self.ca1 = SR570(
46
+ prefix + "-04:",
47
+ suffix=suffix,
48
+ fine_gain_table=SR570FineGainTable,
49
+ coarse_gain_table=SR570GainTable,
50
+ combined_table=SR570FullGainTable,
51
+ gain_to_current_table=SR570GainToCurrentTable,
52
+ raise_timetable=SR570RaiseTimeTable,
53
+ )
54
+ self.ca2 = SR570(
55
+ prefix + "-05:",
56
+ suffix=suffix,
57
+ fine_gain_table=SR570FineGainTable,
58
+ coarse_gain_table=SR570GainTable,
59
+ combined_table=SR570FullGainTable,
60
+ gain_to_current_table=SR570GainToCurrentTable,
61
+ raise_timetable=SR570RaiseTimeTable,
62
+ )
63
+ self.ca3 = SR570(
64
+ prefix + "-06:",
65
+ suffix=suffix,
66
+ fine_gain_table=SR570FineGainTable,
67
+ coarse_gain_table=SR570GainTable,
68
+ combined_table=SR570FullGainTable,
69
+ gain_to_current_table=SR570GainToCurrentTable,
70
+ raise_timetable=SR570RaiseTimeTable,
71
+ )
72
+ super().__init__(name)
@@ -0,0 +1,62 @@
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.motor import Motor
3
+
4
+
5
+ class PinHole(StandardReadable):
6
+ "Two motors stage for rasor pinhole"
7
+
8
+ def __init__(
9
+ self,
10
+ prefix: str,
11
+ name: str = "",
12
+ ):
13
+ with self.add_children_as_readables():
14
+ self.x = Motor(prefix + "X")
15
+ self.y = Motor(prefix + "Y")
16
+ super().__init__(name=name)
17
+
18
+
19
+ class Diffractometer(StandardReadable):
20
+ def __init__(
21
+ self,
22
+ prefix: str,
23
+ name: str = "",
24
+ ):
25
+ with self.add_children_as_readables():
26
+ self.tth = Motor(prefix + "TWOTHETA")
27
+ self.th = Motor(prefix + "THETA")
28
+ self.chi = Motor(prefix + "CHI")
29
+ self.chamber_x = Motor(prefix + "X")
30
+ self.alpha = Motor(prefix + "ALPHA")
31
+ super().__init__(name=name)
32
+
33
+
34
+ class DetSlits(StandardReadable):
35
+ "Detector slits"
36
+
37
+ def __init__(
38
+ self,
39
+ prefix: str,
40
+ name: str = "",
41
+ ):
42
+ with self.add_children_as_readables():
43
+ self.upstream = Motor(prefix + "1:TRANS")
44
+ self.downstream = Motor(prefix + "2:TRANS")
45
+ super().__init__(name=name)
46
+
47
+
48
+ class PaStage(StandardReadable):
49
+ "Rasor detector stage"
50
+
51
+ def __init__(
52
+ self,
53
+ prefix: str,
54
+ name: str = "",
55
+ ):
56
+ with self.add_children_as_readables():
57
+ self.ttp = Motor(prefix + "TWOTHETA")
58
+ self.thp = Motor(prefix + "THETA")
59
+ self.py = Motor(prefix + "Y")
60
+ self.pz = Motor(prefix + "Z")
61
+ self.eta = Motor(prefix + "ETA")
62
+ super().__init__(name=name)
@@ -0,0 +1,12 @@
1
+ from ophyd_async.core import Device
2
+
3
+ from dodal.devices.current_amplifiers import StruckScaler
4
+
5
+
6
+ class RasorScalerCard1(Device):
7
+ def __init__(self, prefix, name: str = "") -> None:
8
+ self.mon = StruckScaler(prefix=prefix, suffix=".16")
9
+ self.det = StruckScaler(prefix=prefix, suffix=".17")
10
+ self.fluo = StruckScaler(prefix=prefix, suffix=".18")
11
+ self.drain = StruckScaler(prefix=prefix, suffix=".19")
12
+ super().__init__(name)
File without changes
@@ -0,0 +1,33 @@
1
+ from ophyd_async.core import PathProvider, StandardDetector
2
+ from ophyd_async.epics import adcore
3
+
4
+ from dodal.devices.i13_1.merlin_controller import MerlinController
5
+ from dodal.devices.i13_1.merlin_io import MerlinDriverIO
6
+
7
+
8
+ class Merlin(StandardDetector):
9
+ _controller: MerlinController
10
+ _writer: adcore.ADHDFWriter
11
+
12
+ def __init__(
13
+ self,
14
+ prefix: str,
15
+ path_provider: PathProvider,
16
+ drv_suffix="CAM:",
17
+ hdf_suffix="HDF:",
18
+ name: str = "",
19
+ ):
20
+ self.drv = MerlinDriverIO(prefix + drv_suffix)
21
+ self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix)
22
+
23
+ super().__init__(
24
+ MerlinController(self.drv),
25
+ adcore.ADHDFWriter(
26
+ self.hdf,
27
+ path_provider,
28
+ lambda: self.name,
29
+ adcore.ADBaseDatasetDescriber(self.drv),
30
+ ),
31
+ config_sigs=(self.drv.acquire_period, self.drv.acquire_time),
32
+ name=name,
33
+ )
@@ -0,0 +1,52 @@
1
+ import asyncio
2
+ import logging
3
+
4
+ from ophyd_async.core import (
5
+ DEFAULT_TIMEOUT,
6
+ AsyncStatus,
7
+ DetectorController,
8
+ TriggerInfo,
9
+ )
10
+ from ophyd_async.epics import adcore
11
+
12
+ from dodal.devices.i13_1.merlin_io import MerlinDriverIO, MerlinImageMode
13
+
14
+
15
+ class MerlinController(DetectorController):
16
+ def __init__(
17
+ self,
18
+ driver: MerlinDriverIO,
19
+ good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
20
+ ) -> None:
21
+ self.driver = driver
22
+ self.good_states = good_states
23
+ self.frame_timeout: float = 0
24
+ self._arm_status: AsyncStatus | None = None
25
+ for drv_child in self.driver.children():
26
+ logging.debug(drv_child)
27
+
28
+ def get_deadtime(self, exposure: float | None) -> float:
29
+ return 0.002
30
+
31
+ async def prepare(self, trigger_info: TriggerInfo):
32
+ self.frame_timeout = (
33
+ DEFAULT_TIMEOUT + await self.driver.acquire_time.get_value()
34
+ )
35
+ await asyncio.gather(
36
+ self.driver.num_images.set(trigger_info.total_number_of_triggers),
37
+ self.driver.image_mode.set(MerlinImageMode.MULTIPLE),
38
+ )
39
+
40
+ async def arm(self):
41
+ self._arm_status = await adcore.start_acquiring_driver_and_ensure_status(
42
+ self.driver, good_states=self.good_states, timeout=self.frame_timeout
43
+ )
44
+
45
+ async def wait_for_idle(self):
46
+ if self._arm_status:
47
+ await self._arm_status
48
+
49
+ async def disarm(self):
50
+ # We can't use caput callback as we already used it in arm() and we can't have
51
+ # 2 or they will deadlock
52
+ await adcore.stop_busy_record(self.driver.acquire, False, timeout=1)
@@ -0,0 +1,17 @@
1
+ from ophyd_async.core import StrictEnum
2
+ from ophyd_async.epics import adcore
3
+ from ophyd_async.epics.core import epics_signal_rw_rbv
4
+
5
+
6
+ class MerlinImageMode(StrictEnum):
7
+ SINGLE = "Single"
8
+ MULTIPLE = "Multiple"
9
+ CONTINUOUS = "Continuous"
10
+ THRESHOLD = "Threshold"
11
+ BACKGROUND = "Background"
12
+
13
+
14
+ class MerlinDriverIO(adcore.ADBaseIO):
15
+ def __init__(self, prefix: str, name: str = "") -> None:
16
+ super().__init__(prefix, name)
17
+ self.image_mode = epics_signal_rw_rbv(MerlinImageMode, prefix + "ImageMode")
@@ -7,6 +7,6 @@ from ophyd_async.epics.core import epics_signal_rw
7
7
 
8
8
  class DetectorBeamCenter(StandardReadable):
9
9
  def __init__(self, prefix: str, name: str = "") -> None:
10
- self.beam_x = epics_signal_rw(float, prefix + "BeamX")
10
+ self.beam_x = epics_signal_rw(float, prefix + "BeamX") # in pixels
11
11
  self.beam_y = epics_signal_rw(float, prefix + "BeamY")
12
12
  super().__init__(name)
dodal/devices/p45.py CHANGED
@@ -1,44 +1,55 @@
1
- from ophyd import Component as Cpt
2
- from ophyd import EpicsMotor, MotorBundle
3
- from ophyd.areadetector.base import ADComponent as Cpt
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.motor import Motor
4
3
 
5
4
 
6
- class SampleY(MotorBundle):
5
+ class SampleY(StandardReadable):
7
6
  """
8
7
  Motors for controlling the sample's y position and stretch in the y axis.
9
8
  """
10
9
 
11
- base = Cpt(EpicsMotor, "CS:Y")
12
- stretch = Cpt(EpicsMotor, "CS:Y:STRETCH")
13
- top = Cpt(EpicsMotor, "Y:TOP")
14
- bottom = Cpt(EpicsMotor, "Y:BOT")
10
+ def __init__(self, prefix: str, name="") -> None:
11
+ with self.add_children_as_readables():
12
+ self.base = Motor(prefix + "CS:Y")
13
+ self.stretch = Motor(prefix + "CS:Y:STRETCH")
14
+ self.top = Motor(prefix + "Y:TOP")
15
+ self.bottom = Motor(prefix + "Y:BOT")
16
+ super().__init__(name=name)
15
17
 
16
18
 
17
- class SampleTheta(MotorBundle):
19
+ class SampleTheta(StandardReadable):
18
20
  """
19
21
  Motors for controlling the sample's theta position and skew
20
22
  """
21
23
 
22
- base = Cpt(EpicsMotor, "THETA:POS")
23
- skew = Cpt(EpicsMotor, "THETA:SKEW")
24
- top = Cpt(EpicsMotor, "THETA:TOP")
25
- bottom = Cpt(EpicsMotor, "THETA:BOT")
24
+ def __init__(self, prefix: str, name="") -> None:
25
+ with self.add_children_as_readables():
26
+ self.base = Motor(prefix + "THETA:POS")
27
+ self.skew = Motor(prefix + "THETA:SKEW")
28
+ self.top = Motor(prefix + "THETA:TOP")
29
+ self.bottom = Motor(prefix + "THETA:BOT")
30
+ super().__init__(name=name)
26
31
 
27
32
 
28
- class TomoStageWithStretchAndSkew(MotorBundle):
33
+ class TomoStageWithStretchAndSkew(StandardReadable):
29
34
  """
30
35
  Grouping of motors for the P45 tomography stage
31
36
  """
32
37
 
33
- x = Cpt(EpicsMotor, "X")
34
- y = Cpt(SampleY, "")
35
- theta = Cpt(SampleTheta, "")
38
+ def __init__(self, prefix: str, name="") -> None:
39
+ with self.add_children_as_readables():
40
+ self.x = Motor(prefix + "X")
41
+ self.y = SampleY(prefix)
42
+ self.theta = SampleTheta(prefix)
43
+ super().__init__(name=name)
36
44
 
37
45
 
38
- class Choppers(MotorBundle):
46
+ class Choppers(StandardReadable):
39
47
  """
40
48
  Grouping for the P45 chopper motors
41
49
  """
42
50
 
43
- x = Cpt(EpicsMotor, "ENDAT")
44
- y = Cpt(EpicsMotor, "BISS")
51
+ def __init__(self, prefix: str, name="") -> None:
52
+ with self.add_children_as_readables():
53
+ self.x = Motor(prefix + "ENDAT")
54
+ self.y = Motor(prefix + "BISS")
55
+ super().__init__(name=name)
@@ -1,5 +1,5 @@
1
- from ophyd_async.core import StandardReadable, SubsetEnum
2
- from ophyd_async.epics.core import epics_signal_rw, epics_signal_rw_rbv
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.core import epics_signal_rw_rbv
3
3
 
4
4
 
5
5
  class SampleAngleStage(StandardReadable):
@@ -9,29 +9,3 @@ class SampleAngleStage(StandardReadable):
9
9
  self.roll = epics_signal_rw_rbv(float, prefix + "WRITEROLL", ":RBV")
10
10
  self.pitch = epics_signal_rw_rbv(float, prefix + "WRITEPITCH", ":RBV")
11
11
  super().__init__(name=name)
12
-
13
-
14
- class p99StageSelections(SubsetEnum):
15
- EMPTY = "Empty"
16
- MN5UM = "Mn 5um"
17
- FE = "Fe (empty)"
18
- CO5UM = "Co 5um"
19
- NI5UM = "Ni 5um"
20
- CU5UM = "Cu 5um"
21
- ZN5UM = "Zn 5um"
22
- ZR = "Zr (empty)"
23
- MO = "Mo (empty)"
24
- RH = "Rh (empty)"
25
- PD = "Pd (empty)"
26
- AG = "Ag (empty)"
27
- CD25UM = "Cd 25um"
28
- W = "W (empty)"
29
- PT = "Pt (empty)"
30
- USER = "User"
31
-
32
-
33
- class FilterMotor(StandardReadable):
34
- def __init__(self, prefix: str, name: str = ""):
35
- with self.add_children_as_readables():
36
- self.user_setpoint = epics_signal_rw(p99StageSelections, prefix)
37
- super().__init__(name=name)
dodal/devices/robot.py CHANGED
@@ -70,8 +70,8 @@ class BartRobot(StandardReadable, Movable):
70
70
  self.program_running = epics_signal_r(bool, prefix + "PROGRAM_RUNNING")
71
71
  self.program_name = epics_signal_r(str, prefix + "PROGRAM_NAME")
72
72
  self.error_str = epics_signal_r(str, prefix + "PRG_ERR_MSG")
73
- # Change error_code to int type when https://github.com/bluesky/ophyd-async/issues/280 released
74
- self.error_code = epics_signal_r(float, prefix + "PRG_ERR_CODE")
73
+ self.error_code = epics_signal_r(int, prefix + "PRG_ERR_CODE")
74
+ self.reset = epics_signal_x(prefix + "RESET.PROC")
75
75
  super().__init__(name=name)
76
76
 
77
77
  async def pin_mounted_or_no_pin_found(self):
@@ -1,8 +1,12 @@
1
- from ophyd import Component, Device, EpicsMotor
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.motor import Motor
2
3
 
3
4
 
4
- class S4SlitGaps(Device):
5
+ class S4SlitGaps(StandardReadable):
5
6
  """Note that the S4 slits have a different PV fromat to other beamline slits"""
6
7
 
7
- xgap = Component(EpicsMotor, "XGAP")
8
- ygap = Component(EpicsMotor, "YGAP")
8
+ def __init__(self, prefix: str, name="") -> None:
9
+ with self.add_children_as_readables():
10
+ self.xgap = Motor(prefix + "XGAP")
11
+ self.ygap = Motor(prefix + "YGAP")
12
+ super().__init__(name=name)
@@ -1,7 +1,7 @@
1
1
  import asyncio
2
2
 
3
3
  from bluesky.protocols import Movable
4
- from ophyd_async.core import AsyncStatus, StandardReadable
4
+ from ophyd_async.core import AsyncStatus, Reference, StandardReadable
5
5
 
6
6
  from dodal.common.beamlines.beamline_parameters import get_beamline_parameters
7
7
 
@@ -33,12 +33,8 @@ class UndulatorDCM(StandardReadable, Movable):
33
33
  prefix: str = "",
34
34
  name: str = "",
35
35
  ):
36
- super().__init__(name)
37
-
38
- # Attributes are set after super call so they are not renamed to
39
- # <name>-undulator, etc.
40
- self.undulator = undulator
41
- self.dcm = dcm
36
+ self.undulator_ref = Reference(undulator)
37
+ self.dcm_ref = Reference(dcm)
42
38
 
43
39
  # These attributes are just used by hyperion for lookup purposes
44
40
  self.pitch_energy_table_path = (
@@ -53,13 +49,15 @@ class UndulatorDCM(StandardReadable, Movable):
53
49
  daq_configuration_path + "/domain/beamlineParameters"
54
50
  )["DCM_Perp_Offset_FIXED"]
55
51
 
52
+ super().__init__(name)
53
+
56
54
  @AsyncStatus.wrap
57
55
  async def set(self, value: float):
58
- await self.undulator.raise_if_not_enabled()
56
+ await self.undulator_ref().raise_if_not_enabled()
59
57
  await asyncio.gather(
60
- self.dcm.energy_in_kev.set(value, timeout=ENERGY_TIMEOUT_S),
61
- self.undulator.set(value),
58
+ self.dcm_ref().energy_in_kev.set(value, timeout=ENERGY_TIMEOUT_S),
59
+ self.undulator_ref().set(value),
62
60
  )
63
61
  # DCM Perp pitch
64
62
  LOGGER.info(f"Adjusting DCM offset to {self.dcm_fixed_offset_mm} mm")
65
- await self.dcm.offset_in_mm.set(self.dcm_fixed_offset_mm)
63
+ await self.dcm_ref().offset_in_mm.set(self.dcm_fixed_offset_mm)
@@ -32,28 +32,32 @@ async def energy_distance_table(lookup_table_path: str) -> np.ndarray:
32
32
  return loadtxt(StringIO(raw_table), comments=["#", "Units"])
33
33
 
34
34
 
35
- def linear_interpolation_lut(filename: str) -> Callable[[float], float]:
35
+ def parse_lookup_table(filename: str) -> list[Sequence]:
36
+ """Parse a generic lookup table with a number of columns >= 2 and return a list \
37
+ in column major order of the values in it."""
38
+ LOGGER.info(f"Parsing lookup table file {filename}")
39
+
40
+ lut_vals = zip(*loadtxt(filename, comments=["#", "Units"]), strict=False)
41
+ return list(lut_vals)
42
+
43
+
44
+ def linear_interpolation_lut(
45
+ s_values: Sequence, t_values: Sequence
46
+ ) -> Callable[[float], float]:
36
47
  """Returns a callable that converts values by linear interpolation of lookup table
37
48
  values.
38
49
 
39
50
  If the value falls outside the lookup table then the closest value will be used."""
40
- LOGGER.info(f"Using lookup table {filename}")
41
- s_and_t_vals = zip(*loadtxt(filename, comments=["#", "Units"]), strict=False)
42
-
43
- s_values: Sequence
44
- t_values: Sequence
45
- s_values, t_values = s_and_t_vals
46
-
47
51
  # numpy interp expects x-values to be increasing
48
52
  if not np.all(np.diff(s_values) > 0):
49
53
  LOGGER.info(
50
- f"Configuration file {filename} values are not ascending, trying reverse order..."
54
+ "Configuration values in the lookup table are not ascending, trying reverse order..."
51
55
  )
52
56
  s_values = list(reversed(s_values))
53
57
  t_values = list(reversed(t_values))
54
58
  if not np.all(np.diff(s_values) > 0):
55
59
  raise AssertionError(
56
- f"Configuration file {filename} lookup table does not monotonically increase or decrease."
60
+ "Configuration lookup table does not monotonically increase or decrease."
57
61
  )
58
62
 
59
63
  def s_to_t2(s: float) -> float:
File without changes