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.
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/METADATA +4 -3
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/RECORD +58 -38
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/WHEEL +1 -1
- dodal/_version.py +2 -2
- dodal/beamlines/i02_1.py +37 -0
- dodal/beamlines/i03.py +34 -5
- dodal/beamlines/i04.py +16 -5
- dodal/beamlines/i10.py +105 -0
- dodal/beamlines/i13_1.py +20 -2
- dodal/beamlines/i22.py +15 -0
- dodal/beamlines/i24.py +14 -2
- dodal/beamlines/p99.py +6 -2
- dodal/beamlines/training_rig.py +10 -1
- dodal/common/crystal_metadata.py +3 -3
- dodal/common/udc_directory_provider.py +3 -1
- dodal/devices/aperturescatterguard.py +3 -0
- dodal/devices/{attenuator.py → attenuator/attenuator.py} +29 -1
- dodal/devices/attenuator/filter.py +11 -0
- dodal/devices/attenuator/filter_selections.py +72 -0
- dodal/devices/bimorph_mirror.py +151 -0
- dodal/devices/current_amplifiers/__init__.py +34 -0
- dodal/devices/current_amplifiers/current_amplifier.py +103 -0
- dodal/devices/current_amplifiers/current_amplifier_detector.py +109 -0
- dodal/devices/current_amplifiers/femto.py +143 -0
- dodal/devices/current_amplifiers/sr570.py +214 -0
- dodal/devices/current_amplifiers/struck_scaler_counter.py +79 -0
- dodal/devices/detector/det_dim_constants.py +15 -0
- dodal/devices/eiger_odin.py +3 -3
- dodal/devices/fast_grid_scan.py +8 -3
- dodal/devices/flux.py +10 -3
- dodal/devices/i03/beamstop.py +85 -0
- dodal/devices/i04/transfocator.py +67 -53
- dodal/devices/i10/rasor/rasor_current_amp.py +72 -0
- dodal/devices/i10/rasor/rasor_motors.py +62 -0
- dodal/devices/i10/rasor/rasor_scaler_cards.py +12 -0
- dodal/devices/i13_1/__init__.py +0 -0
- dodal/devices/i13_1/merlin.py +33 -0
- dodal/devices/i13_1/merlin_controller.py +52 -0
- dodal/devices/i13_1/merlin_io.py +17 -0
- dodal/devices/i24/beam_center.py +1 -1
- dodal/devices/p45.py +31 -20
- dodal/devices/p99/sample_stage.py +2 -28
- dodal/devices/robot.py +2 -2
- dodal/devices/s4_slit_gaps.py +8 -4
- dodal/devices/undulator_dcm.py +9 -11
- dodal/devices/util/lookup_tables.py +14 -10
- dodal/devices/zebra/__init__.py +0 -0
- dodal/devices/{zebra.py → zebra/zebra.py} +9 -33
- dodal/devices/zebra/zebra_constants_mapping.py +96 -0
- dodal/devices/zocalo/zocalo_interaction.py +2 -1
- dodal/devices/zocalo/zocalo_results.py +22 -2
- dodal/log.py +2 -2
- dodal/plans/wrapped.py +3 -3
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/LICENSE +0 -0
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/top_level.txt +0 -0
- /dodal/devices/{zebra_controlled_shutter.py → zebra/zebra_controlled_shutter.py} +0 -0
- /dodal/{devices/util → plans}/save_panda.py +0 -0
dodal/beamlines/i24.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from dodal.common.beamlines.beamline_utils import BL, device_instantiation
|
|
2
2
|
from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
|
|
3
|
-
from dodal.devices.attenuator import ReadOnlyAttenuator
|
|
3
|
+
from dodal.devices.attenuator.attenuator import ReadOnlyAttenuator
|
|
4
4
|
from dodal.devices.detector import DetectorParams
|
|
5
5
|
from dodal.devices.eiger import EigerDetector
|
|
6
6
|
from dodal.devices.hutch_shutter import HutchShutter
|
|
@@ -16,7 +16,12 @@ from dodal.devices.i24.pmac import PMAC
|
|
|
16
16
|
from dodal.devices.i24.vgonio import VerticalGoniometer
|
|
17
17
|
from dodal.devices.oav.oav_detector import OAV
|
|
18
18
|
from dodal.devices.oav.oav_parameters import OAVConfig
|
|
19
|
-
from dodal.devices.zebra import Zebra
|
|
19
|
+
from dodal.devices.zebra.zebra import Zebra
|
|
20
|
+
from dodal.devices.zebra.zebra_constants_mapping import (
|
|
21
|
+
ZebraMapping,
|
|
22
|
+
ZebraSources,
|
|
23
|
+
ZebraTTLOutputs,
|
|
24
|
+
)
|
|
20
25
|
from dodal.log import set_beamline as set_log_beamline
|
|
21
26
|
from dodal.utils import get_beamline_name, skip_device
|
|
22
27
|
|
|
@@ -25,10 +30,16 @@ ZOOM_PARAMS_FILE = (
|
|
|
25
30
|
)
|
|
26
31
|
DISPLAY_CONFIG = "/dls_sw/i24/software/gda_versions/var/display.configuration"
|
|
27
32
|
|
|
33
|
+
|
|
28
34
|
BL = get_beamline_name("s24")
|
|
29
35
|
set_log_beamline(BL)
|
|
30
36
|
set_utils_beamline(BL)
|
|
31
37
|
|
|
38
|
+
I24_ZEBRA_MAPPING = ZebraMapping(
|
|
39
|
+
outputs=ZebraTTLOutputs(TTL_EIGER=1, TTL_PILATUS=2, TTL_FAST_SHUTTER=4),
|
|
40
|
+
sources=ZebraSources(),
|
|
41
|
+
)
|
|
42
|
+
|
|
32
43
|
|
|
33
44
|
def attenuator(
|
|
34
45
|
wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
|
|
@@ -191,6 +202,7 @@ def zebra(wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False) -
|
|
|
191
202
|
"-EA-ZEBRA-01:",
|
|
192
203
|
wait_for_connection,
|
|
193
204
|
fake_with_ophyd_sim,
|
|
205
|
+
mapping=I24_ZEBRA_MAPPING,
|
|
194
206
|
)
|
|
195
207
|
|
|
196
208
|
|
dodal/beamlines/p99.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from dodal.common.beamlines.beamline_utils import device_factory, set_beamline
|
|
2
|
+
from dodal.devices.attenuator.filter import FilterMotor
|
|
3
|
+
from dodal.devices.attenuator.filter_selections import P99FilterSelections
|
|
2
4
|
from dodal.devices.motors import XYZPositioner
|
|
3
|
-
from dodal.devices.p99.sample_stage import
|
|
5
|
+
from dodal.devices.p99.sample_stage import SampleAngleStage
|
|
4
6
|
from dodal.log import set_beamline as set_log_beamline
|
|
5
7
|
from dodal.utils import BeamlinePrefix, get_beamline_name
|
|
6
8
|
|
|
@@ -17,7 +19,9 @@ def angle_stage() -> SampleAngleStage:
|
|
|
17
19
|
|
|
18
20
|
@device_factory()
|
|
19
21
|
def filter() -> FilterMotor:
|
|
20
|
-
return FilterMotor(
|
|
22
|
+
return FilterMotor(
|
|
23
|
+
f"{PREFIX.beamline_prefix}-MO-STAGE-02:MP:SELECT", P99FilterSelections
|
|
24
|
+
)
|
|
21
25
|
|
|
22
26
|
|
|
23
27
|
@device_factory()
|
dodal/beamlines/training_rig.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
from ophyd_async.epics.adaravis import AravisDetector
|
|
4
|
+
from ophyd_async.fastcs.panda import HDFPanda
|
|
4
5
|
|
|
5
6
|
from dodal.common.beamlines.beamline_utils import (
|
|
6
7
|
device_factory,
|
|
@@ -33,7 +34,7 @@ set_utils_beamline(BL)
|
|
|
33
34
|
set_path_provider(
|
|
34
35
|
StaticVisitPathProvider(
|
|
35
36
|
BL,
|
|
36
|
-
Path("/
|
|
37
|
+
Path("/data"),
|
|
37
38
|
client=LocalDirectoryServiceClient(),
|
|
38
39
|
)
|
|
39
40
|
)
|
|
@@ -52,3 +53,11 @@ def det() -> AravisDetector:
|
|
|
52
53
|
drv_suffix="DET:",
|
|
53
54
|
hdf_suffix=HDF5_PREFIX,
|
|
54
55
|
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@device_factory()
|
|
59
|
+
def panda() -> HDFPanda:
|
|
60
|
+
return HDFPanda(
|
|
61
|
+
prefix=f"{PREFIX.beamline_prefix}-MO-PANDA-01:",
|
|
62
|
+
path_provider=get_path_provider(),
|
|
63
|
+
)
|
dodal/common/crystal_metadata.py
CHANGED
|
@@ -55,7 +55,7 @@ def make_crystal_metadata_from_material(
|
|
|
55
55
|
d_spacing = d_spacing_param or CrystalMetadata.calculate_default_d_spacing(
|
|
56
56
|
material.value.lattice_parameter, reflection_plane
|
|
57
57
|
)
|
|
58
|
-
assert all(
|
|
59
|
-
|
|
60
|
-
)
|
|
58
|
+
assert all(isinstance(i, int) and i > 0 for i in reflection_plane), (
|
|
59
|
+
"Reflection plane indices must be positive integers"
|
|
60
|
+
)
|
|
61
61
|
return CrystalMetadata(usage, material.value.name, reflection_plane, d_spacing)
|
|
@@ -46,7 +46,9 @@ class PandASubpathProvider(UpdatingPathProvider):
|
|
|
46
46
|
self._filename_provider.suffix = suffix
|
|
47
47
|
|
|
48
48
|
def __call__(self, device_name: str | None = None) -> PathInfo:
|
|
49
|
-
assert self._output_directory,
|
|
49
|
+
assert self._output_directory, (
|
|
50
|
+
"Directory unknown for PandA to write into, update() needs to be called at least once"
|
|
51
|
+
)
|
|
50
52
|
return PathInfo(
|
|
51
53
|
directory_path=self._output_directory,
|
|
52
54
|
filename=self._filename_provider(device_name),
|
|
@@ -7,10 +7,12 @@ from ophyd_async.core import (
|
|
|
7
7
|
DeviceVector,
|
|
8
8
|
SignalR,
|
|
9
9
|
StandardReadable,
|
|
10
|
+
SubsetEnum,
|
|
10
11
|
wait_for_value,
|
|
11
12
|
)
|
|
12
13
|
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_x
|
|
13
14
|
|
|
15
|
+
from dodal.devices.attenuator.filter import FilterMotor
|
|
14
16
|
from dodal.log import LOGGER
|
|
15
17
|
|
|
16
18
|
|
|
@@ -27,8 +29,9 @@ class ReadOnlyAttenuator(StandardReadable):
|
|
|
27
29
|
super().__init__(name)
|
|
28
30
|
|
|
29
31
|
|
|
30
|
-
class
|
|
32
|
+
class BinaryFilterAttenuator(ReadOnlyAttenuator, Movable):
|
|
31
33
|
"""The attenuator will insert filters into the beam to reduce its transmission.
|
|
34
|
+
In this attenuator, each filter can be in one of two states: IN or OUT
|
|
32
35
|
|
|
33
36
|
This device should be set with:
|
|
34
37
|
yield from bps.set(attenuator, desired_transmission)
|
|
@@ -83,3 +86,28 @@ class Attenuator(ReadOnlyAttenuator, Movable):
|
|
|
83
86
|
for i in range(16)
|
|
84
87
|
]
|
|
85
88
|
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class EnumFilterAttenuator(ReadOnlyAttenuator):
|
|
92
|
+
"""The attenuator will insert filters into the beam to reduce its transmission.
|
|
93
|
+
|
|
94
|
+
This device is currently working, but feature incomplete. See https://github.com/DiamondLightSource/dodal/issues/972
|
|
95
|
+
|
|
96
|
+
In this attenuator, the state of a filter corresponds to the selected material,
|
|
97
|
+
e.g Ag50, in contrast to being either 'IN' or 'OUT'; see BinaryFilterAttenuator.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
prefix: str,
|
|
103
|
+
filter_selection: tuple[type[SubsetEnum], ...],
|
|
104
|
+
name: str = "",
|
|
105
|
+
):
|
|
106
|
+
with self.add_children_as_readables():
|
|
107
|
+
self.filters: DeviceVector[FilterMotor] = DeviceVector(
|
|
108
|
+
{
|
|
109
|
+
index: FilterMotor(f"{prefix}MP{index + 1}:", filter, name)
|
|
110
|
+
for index, filter in enumerate(filter_selection)
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
super().__init__(prefix, name=name)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from ophyd_async.core import StandardReadable, SubsetEnum
|
|
2
|
+
from ophyd_async.epics.core import epics_signal_rw
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FilterMotor(StandardReadable):
|
|
6
|
+
def __init__(
|
|
7
|
+
self, prefix: str, filter_selections: type[SubsetEnum], name: str = ""
|
|
8
|
+
):
|
|
9
|
+
with self.add_children_as_readables():
|
|
10
|
+
self.user_setpoint = epics_signal_rw(filter_selections, f"{prefix}SELECT")
|
|
11
|
+
super().__init__(name=name)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from ophyd_async.core import SubsetEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class P99FilterSelections(SubsetEnum):
|
|
5
|
+
EMPTY = "Empty"
|
|
6
|
+
MN5UM = "Mn 5um"
|
|
7
|
+
FE = "Fe (empty)"
|
|
8
|
+
CO5UM = "Co 5um"
|
|
9
|
+
NI5UM = "Ni 5um"
|
|
10
|
+
CU5UM = "Cu 5um"
|
|
11
|
+
ZN5UM = "Zn 5um"
|
|
12
|
+
ZR = "Zr (empty)"
|
|
13
|
+
MO = "Mo (empty)"
|
|
14
|
+
RH = "Rh (empty)"
|
|
15
|
+
PD = "Pd (empty)"
|
|
16
|
+
AG = "Ag (empty)"
|
|
17
|
+
CD25UM = "Cd 25um"
|
|
18
|
+
W = "W (empty)"
|
|
19
|
+
PT = "Pt (empty)"
|
|
20
|
+
USER = "User"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class I02_1FilterOneSelections(SubsetEnum):
|
|
24
|
+
EMPTY = "Empty"
|
|
25
|
+
AL8 = "Al8"
|
|
26
|
+
AL15 = "Al15"
|
|
27
|
+
AL25 = "Al25"
|
|
28
|
+
AL1000 = "Al1000"
|
|
29
|
+
TI50 = "Ti50"
|
|
30
|
+
TI100 = "Ti100"
|
|
31
|
+
TI200 = "Ti200"
|
|
32
|
+
TI400 = "Ti400"
|
|
33
|
+
TWO_TIMES_TI500 = "2xTi500"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class I02_1FilterTwoSelections(SubsetEnum):
|
|
37
|
+
EMPTY = "Empty"
|
|
38
|
+
AL50 = "Al50"
|
|
39
|
+
AL100 = "Al100"
|
|
40
|
+
AL125 = "Al125"
|
|
41
|
+
AL250 = "Al250"
|
|
42
|
+
AL500 = "Al500"
|
|
43
|
+
AL1000 = "Al1000"
|
|
44
|
+
TI50 = "Ti50"
|
|
45
|
+
TI100 = "Ti100"
|
|
46
|
+
TWO_TIMES_TI500 = "2xTi500"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class I02_1FilterThreeSelections(SubsetEnum):
|
|
50
|
+
EMPTY = "Empty"
|
|
51
|
+
AL15 = "Al15"
|
|
52
|
+
AL25 = "Al25"
|
|
53
|
+
AL50 = "Al50"
|
|
54
|
+
AL100 = "Al100"
|
|
55
|
+
AL250 = "Al250"
|
|
56
|
+
AL1000 = "Al1000"
|
|
57
|
+
TI50 = "Ti50"
|
|
58
|
+
TI100 = "Ti100"
|
|
59
|
+
TI200 = "Ti200"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class I02_1FilterFourSelections(SubsetEnum):
|
|
63
|
+
EMPTY = "Empty"
|
|
64
|
+
AL15 = "Al15"
|
|
65
|
+
AL25 = "Al25"
|
|
66
|
+
AL50 = "Al50"
|
|
67
|
+
AL100 = "Al100"
|
|
68
|
+
AL250 = "Al250"
|
|
69
|
+
AL500 = "Al500"
|
|
70
|
+
TI300 = "Ti300"
|
|
71
|
+
TI400 = "Ti400"
|
|
72
|
+
TI500 = "Ti500"
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections.abc import Mapping
|
|
3
|
+
from typing import Annotated as A
|
|
4
|
+
|
|
5
|
+
from bluesky.protocols import Movable
|
|
6
|
+
from ophyd_async.core import (
|
|
7
|
+
DEFAULT_TIMEOUT,
|
|
8
|
+
AsyncStatus,
|
|
9
|
+
DeviceVector,
|
|
10
|
+
SignalR,
|
|
11
|
+
SignalRW,
|
|
12
|
+
SignalW,
|
|
13
|
+
StandardReadable,
|
|
14
|
+
StrictEnum,
|
|
15
|
+
wait_for_value,
|
|
16
|
+
)
|
|
17
|
+
from ophyd_async.core import StandardReadableFormat as Format
|
|
18
|
+
from ophyd_async.epics.core import (
|
|
19
|
+
EpicsDevice,
|
|
20
|
+
PvSuffix,
|
|
21
|
+
epics_signal_r,
|
|
22
|
+
epics_signal_w,
|
|
23
|
+
epics_signal_x,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BimorphMirrorOnOff(StrictEnum):
|
|
28
|
+
ON = "ON"
|
|
29
|
+
OFF = "OFF"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BimorphMirrorMode(StrictEnum):
|
|
33
|
+
HI = "HI"
|
|
34
|
+
NORMAL = "NORMAL"
|
|
35
|
+
FAST = "FAST"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BimorphMirrorStatus(StrictEnum):
|
|
39
|
+
IDLE = "Idle"
|
|
40
|
+
BUSY = "Busy"
|
|
41
|
+
ERROR = "Error"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BimorphMirrorChannel(StandardReadable, Movable, EpicsDevice):
|
|
45
|
+
"""Collection of PVs comprising a single bimorph channel.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
target_voltage: Float RW_RBV for target voltage, which can be set using parent mirror's all target proc
|
|
49
|
+
output_voltage: Float RW_RBV for current voltage on bimorph
|
|
50
|
+
status: BimorphMirrorOnOff readable for ON/OFF status of channel
|
|
51
|
+
shift: Float writeable shifting channel voltage
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
target_voltage: A[SignalRW[float], PvSuffix.rbv("VTRGT"), Format.CONFIG_SIGNAL]
|
|
55
|
+
output_voltage: A[SignalRW[float], PvSuffix.rbv("VOUT"), Format.HINTED_SIGNAL]
|
|
56
|
+
status: A[SignalR[BimorphMirrorOnOff], PvSuffix("STATUS"), Format.CONFIG_SIGNAL]
|
|
57
|
+
shift: A[SignalW[float], PvSuffix("SHIFT")]
|
|
58
|
+
|
|
59
|
+
@AsyncStatus.wrap
|
|
60
|
+
async def set(self, value: float):
|
|
61
|
+
"""Sets channel's VOUT to given value.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
value: float to set VOUT to
|
|
65
|
+
"""
|
|
66
|
+
await self.output_voltage.set(value)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class BimorphMirror(StandardReadable, Movable):
|
|
70
|
+
"""Class to represent CAENels Bimorph Mirrors.
|
|
71
|
+
|
|
72
|
+
Attributes:
|
|
73
|
+
channels: DeviceVector of BimorphMirrorChannel, indexed from 1, for each channel
|
|
74
|
+
enabled: Writeable BimorphOnOff
|
|
75
|
+
commit_target_voltages: Procable signal that writes values in each channel's VTRGT to VOUT
|
|
76
|
+
status: Readable BimorphMirrorStatus Busy/Idle status
|
|
77
|
+
err: Alarm status"""
|
|
78
|
+
|
|
79
|
+
def __init__(self, prefix: str, number_of_channels: int, name=""):
|
|
80
|
+
"""
|
|
81
|
+
Args:
|
|
82
|
+
prefix: str PV prefix
|
|
83
|
+
number_of_channels: int number of channels on bimorph mirror (can be zero)
|
|
84
|
+
name: str name of device
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
ValueError: number_of_channels is less than zero"""
|
|
88
|
+
|
|
89
|
+
if number_of_channels < 0:
|
|
90
|
+
raise ValueError(f"Number of channels is below zero: {number_of_channels}")
|
|
91
|
+
|
|
92
|
+
with self.add_children_as_readables():
|
|
93
|
+
self.channels = DeviceVector(
|
|
94
|
+
{
|
|
95
|
+
i: BimorphMirrorChannel(f"{prefix}C{i}:")
|
|
96
|
+
for i in range(1, number_of_channels + 1)
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
self.enabled = epics_signal_w(BimorphMirrorOnOff, f"{prefix}ONOFF")
|
|
100
|
+
self.commit_target_voltages = epics_signal_x(f"{prefix}ALLTRGT.PROC")
|
|
101
|
+
self.status = epics_signal_r(BimorphMirrorStatus, f"{prefix}STATUS")
|
|
102
|
+
self.err = epics_signal_r(str, f"{prefix}ERR")
|
|
103
|
+
super().__init__(name=name)
|
|
104
|
+
|
|
105
|
+
@AsyncStatus.wrap
|
|
106
|
+
async def set(self, value: Mapping[int, float], tolerance: float = 0.0001) -> None:
|
|
107
|
+
"""Sets bimorph voltages in parrallel via target voltage and all proc.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
value: Dict of channel numbers to target voltages
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
ValueError: On set to non-existent channel"""
|
|
114
|
+
|
|
115
|
+
if any(key not in self.channels for key in value):
|
|
116
|
+
raise ValueError(
|
|
117
|
+
f"Attempting to put to non-existent channels: {[key for key in value if (key not in self.channels)]}"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Write target voltages:
|
|
121
|
+
await asyncio.gather(
|
|
122
|
+
*[
|
|
123
|
+
self.channels[i].target_voltage.set(target, wait=True)
|
|
124
|
+
for i, target in value.items()
|
|
125
|
+
]
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Trigger set target voltages:
|
|
129
|
+
await self.commit_target_voltages.trigger()
|
|
130
|
+
|
|
131
|
+
# Wait for values to propogate to voltage out rbv:
|
|
132
|
+
await asyncio.gather(
|
|
133
|
+
*[
|
|
134
|
+
wait_for_value(
|
|
135
|
+
self.channels[i].output_voltage,
|
|
136
|
+
tolerance_func_builder(tolerance, target),
|
|
137
|
+
timeout=DEFAULT_TIMEOUT,
|
|
138
|
+
)
|
|
139
|
+
for i, target in value.items()
|
|
140
|
+
],
|
|
141
|
+
wait_for_value(
|
|
142
|
+
self.status, BimorphMirrorStatus.IDLE, timeout=DEFAULT_TIMEOUT
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def tolerance_func_builder(tolerance: float, target_value: float):
|
|
148
|
+
def is_within_value(x):
|
|
149
|
+
return abs(x - target_value) <= tolerance
|
|
150
|
+
|
|
151
|
+
return is_within_value
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from .current_amplifier import CurrentAmp
|
|
2
|
+
from .current_amplifier_detector import CurrentAmpCounter, CurrentAmpDet
|
|
3
|
+
from .femto import (
|
|
4
|
+
Femto3xxGainTable,
|
|
5
|
+
Femto3xxGainToCurrentTable,
|
|
6
|
+
Femto3xxRaiseTime,
|
|
7
|
+
FemtoDDPCA,
|
|
8
|
+
)
|
|
9
|
+
from .sr570 import (
|
|
10
|
+
SR570,
|
|
11
|
+
SR570FineGainTable,
|
|
12
|
+
SR570FullGainTable,
|
|
13
|
+
SR570GainTable,
|
|
14
|
+
SR570GainToCurrentTable,
|
|
15
|
+
SR570RaiseTimeTable,
|
|
16
|
+
)
|
|
17
|
+
from .struck_scaler_counter import StruckScaler
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"FemtoDDPCA",
|
|
21
|
+
"Femto3xxGainTable",
|
|
22
|
+
"Femto3xxRaiseTime",
|
|
23
|
+
"CurrentAmp",
|
|
24
|
+
"Femto3xxGainToCurrentTable",
|
|
25
|
+
"CurrentAmpCounter",
|
|
26
|
+
"CurrentAmpDet",
|
|
27
|
+
"SR570",
|
|
28
|
+
"SR570GainTable",
|
|
29
|
+
"SR570FineGainTable",
|
|
30
|
+
"SR570FullGainTable",
|
|
31
|
+
"SR570GainToCurrentTable",
|
|
32
|
+
"SR570RaiseTimeTable",
|
|
33
|
+
"StruckScaler",
|
|
34
|
+
]
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
from bluesky.protocols import (
|
|
5
|
+
Movable,
|
|
6
|
+
Preparable,
|
|
7
|
+
)
|
|
8
|
+
from ophyd_async.core import AsyncStatus, StandardReadable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CurrentAmp(ABC, StandardReadable, Movable):
|
|
12
|
+
"""
|
|
13
|
+
Base class for current amplifier, it contains the minimal functionality
|
|
14
|
+
a current amplifier needs:
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
gain_conversion_table (Enum): The conversion table between current
|
|
18
|
+
and gain setting.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, gain_conversion_table: type[Enum], name: str = "") -> None:
|
|
22
|
+
self.gain_conversion_table = gain_conversion_table
|
|
23
|
+
super().__init__(name)
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
@AsyncStatus.wrap
|
|
27
|
+
async def increase_gain(self, value: int = 1) -> None:
|
|
28
|
+
"""Increase gain, increment by 1 by default.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
bool: True if success.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@AsyncStatus.wrap
|
|
35
|
+
@abstractmethod
|
|
36
|
+
async def decrease_gain(self, value: int = 1) -> None:
|
|
37
|
+
"""Decrease gain, decrement by 1 by default.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
bool: True if success.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
@AsyncStatus.wrap
|
|
44
|
+
@abstractmethod
|
|
45
|
+
async def get_gain(self) -> type[Enum]:
|
|
46
|
+
"""Get the current gain setting
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Enum: The member name of the current gain setting in gain_conversion_table.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
@AsyncStatus.wrap
|
|
53
|
+
@abstractmethod
|
|
54
|
+
async def get_upperlimit(self) -> float:
|
|
55
|
+
"""Get the upper limit of the current amplifier"""
|
|
56
|
+
|
|
57
|
+
@AsyncStatus.wrap
|
|
58
|
+
@abstractmethod
|
|
59
|
+
async def get_lowerlimit(self) -> float:
|
|
60
|
+
"""Get the lower limit of the current amplifier"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CurrentAmpCounter(ABC, StandardReadable, Preparable):
|
|
64
|
+
"""
|
|
65
|
+
Base class for current amplifier counter, it contain the minimal implementations
|
|
66
|
+
required for a counter/detector to function with CurrentAmpDet:
|
|
67
|
+
|
|
68
|
+
Attributes:
|
|
69
|
+
count_per_volt (float): The conversion factor between counter output and voltage.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, count_per_volt: float, name: str = ""):
|
|
73
|
+
self.count_per_volt = count_per_volt
|
|
74
|
+
super().__init__(name)
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
async def get_count(self) -> float:
|
|
78
|
+
""" "Get count
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
float: Current count
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
@abstractmethod
|
|
85
|
+
async def get_count_per_sec(self) -> float:
|
|
86
|
+
"""Get count per second
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
float: Current count per second
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
async def get_voltage_per_sec(self) -> float:
|
|
94
|
+
"""Get count per second in voltage
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
float: Current count in volt per second
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
@abstractmethod
|
|
101
|
+
@AsyncStatus.wrap
|
|
102
|
+
async def prepare(self, value: float) -> None:
|
|
103
|
+
"""Prepare method for setting up the counter"""
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from bluesky.protocols import Preparable, Reading
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
AsyncStatus,
|
|
6
|
+
Reference,
|
|
7
|
+
StandardReadable,
|
|
8
|
+
StandardReadableFormat,
|
|
9
|
+
soft_signal_r_and_setter,
|
|
10
|
+
soft_signal_rw,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from dodal.devices.current_amplifiers.current_amplifier import (
|
|
14
|
+
CurrentAmp,
|
|
15
|
+
CurrentAmpCounter,
|
|
16
|
+
)
|
|
17
|
+
from dodal.log import LOGGER
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CurrentAmpDet(StandardReadable, Preparable):
|
|
21
|
+
"""
|
|
22
|
+
CurrentAmpDet composed of a CurrentAmp and a CurrentAmpCounter. It provides
|
|
23
|
+
the option for automatically changing the CurrentAmp gain to within the optimal
|
|
24
|
+
range. It also converts the currentAmp/counter output back into the detector
|
|
25
|
+
current output in Amp.
|
|
26
|
+
Attributes:
|
|
27
|
+
current_amp (currentAmp): Current amplifier type device.
|
|
28
|
+
counter (CurrentAmpCounter): Counter that capture the current amplifier output.
|
|
29
|
+
current (SignalRW([float]): Soft signal to store the corrected current.
|
|
30
|
+
auto_mode (signalR([bool])): Soft signal to store the flag for auto gain.
|
|
31
|
+
name (str): Name of the device.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
current_amp: CurrentAmp,
|
|
37
|
+
counter: CurrentAmpCounter,
|
|
38
|
+
name: str = "",
|
|
39
|
+
) -> None:
|
|
40
|
+
self.current_amp = Reference(current_amp)
|
|
41
|
+
self.counter = Reference(counter)
|
|
42
|
+
with self.add_children_as_readables():
|
|
43
|
+
self.current, self._set_current = soft_signal_r_and_setter(
|
|
44
|
+
float, initial_value=None, units="Amp"
|
|
45
|
+
)
|
|
46
|
+
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
47
|
+
self.auto_mode = soft_signal_rw(bool, initial_value=True)
|
|
48
|
+
super().__init__(name)
|
|
49
|
+
|
|
50
|
+
async def read(self) -> dict[str, Reading]:
|
|
51
|
+
"""
|
|
52
|
+
Read is modified so that if auto_mode is true it will optimise gain before
|
|
53
|
+
taking the final reading
|
|
54
|
+
"""
|
|
55
|
+
if await self.auto_mode.get_value():
|
|
56
|
+
LOGGER.info(f"{self.name}-Attempting auto-gain")
|
|
57
|
+
status = self.auto_gain()
|
|
58
|
+
try:
|
|
59
|
+
await status
|
|
60
|
+
LOGGER.info(
|
|
61
|
+
f"{self.name} new gain = {await self.current_amp().get_gain()}."
|
|
62
|
+
)
|
|
63
|
+
except ValueError as ex:
|
|
64
|
+
LOGGER.warning(f"{self.name} gain went outside limits")
|
|
65
|
+
# Further details are provided to the user in the logged exception
|
|
66
|
+
LOGGER.exception(ex)
|
|
67
|
+
current = await self.get_corrected_current()
|
|
68
|
+
self._set_current(current)
|
|
69
|
+
return await super().read()
|
|
70
|
+
|
|
71
|
+
@AsyncStatus.wrap
|
|
72
|
+
async def auto_gain(self) -> None:
|
|
73
|
+
within_limits = False
|
|
74
|
+
while not within_limits:
|
|
75
|
+
reading = abs(await self.counter().get_voltage_per_sec())
|
|
76
|
+
upper_limit, lower_limit = await asyncio.gather(
|
|
77
|
+
self.current_amp().get_upperlimit(),
|
|
78
|
+
self.current_amp().get_lowerlimit(),
|
|
79
|
+
)
|
|
80
|
+
if reading > upper_limit:
|
|
81
|
+
await self.current_amp().decrease_gain()
|
|
82
|
+
elif reading < lower_limit:
|
|
83
|
+
await self.current_amp().increase_gain()
|
|
84
|
+
else:
|
|
85
|
+
within_limits = True
|
|
86
|
+
|
|
87
|
+
async def get_corrected_current(self) -> float:
|
|
88
|
+
"""
|
|
89
|
+
Convert the output(count and gain) back into the read detector output in Amp.
|
|
90
|
+
"""
|
|
91
|
+
current_gain, voltage_per_sec = await asyncio.gather(
|
|
92
|
+
self.current_amp().get_gain(),
|
|
93
|
+
self.counter().get_voltage_per_sec(),
|
|
94
|
+
)
|
|
95
|
+
correction_factor = current_gain.value
|
|
96
|
+
corrected_current = voltage_per_sec / correction_factor
|
|
97
|
+
return corrected_current
|
|
98
|
+
|
|
99
|
+
@AsyncStatus.wrap
|
|
100
|
+
async def stage(self) -> None:
|
|
101
|
+
await self.counter().stage()
|
|
102
|
+
|
|
103
|
+
@AsyncStatus.wrap
|
|
104
|
+
async def unstage(self) -> None:
|
|
105
|
+
await self.counter().unstage()
|
|
106
|
+
|
|
107
|
+
@AsyncStatus.wrap
|
|
108
|
+
async def prepare(self, value) -> None:
|
|
109
|
+
await self.counter().prepare(value=value)
|