dls-dodal 1.36.3__py3-none-any.whl → 1.37.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.37.0.dist-info}/METADATA +3 -3
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.0.dist-info}/RECORD +43 -29
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.0.dist-info}/WHEEL +1 -1
- dodal/_version.py +2 -2
- dodal/beamlines/i02_1.py +37 -0
- dodal/beamlines/i03.py +20 -3
- dodal/beamlines/i04.py +3 -3
- dodal/beamlines/i10.py +105 -0
- dodal/beamlines/i22.py +15 -0
- dodal/beamlines/i24.py +1 -1
- dodal/beamlines/p99.py +6 -2
- 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/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/p99/sample_stage.py +2 -28
- dodal/devices/robot.py +2 -2
- dodal/devices/undulator_dcm.py +9 -11
- dodal/devices/zebra.py +6 -1
- 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.37.0.dist-info}/LICENSE +0 -0
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.0.dist-info}/top_level.txt +0 -0
|
@@ -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)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
AsyncStatus,
|
|
6
|
+
StandardReadableFormat,
|
|
7
|
+
StrictEnum,
|
|
8
|
+
)
|
|
9
|
+
from ophyd_async.epics.core import epics_signal_rw
|
|
10
|
+
|
|
11
|
+
from dodal.devices.current_amplifiers import CurrentAmp
|
|
12
|
+
from dodal.log import LOGGER
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Femto3xxGainTable(StrictEnum):
|
|
16
|
+
"""These are the sensitivity setting for Femto 3xx current amplifier"""
|
|
17
|
+
|
|
18
|
+
SEN_1 = "10^4"
|
|
19
|
+
SEN_2 = "10^5"
|
|
20
|
+
SEN_3 = "10^6"
|
|
21
|
+
SEN_4 = "10^7"
|
|
22
|
+
SEN_5 = "10^8"
|
|
23
|
+
SEN_6 = "10^9"
|
|
24
|
+
SEN_7 = "10^10"
|
|
25
|
+
SEN_8 = "10^11"
|
|
26
|
+
SEN_9 = "10^12"
|
|
27
|
+
SEN_10 = "10^13"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Femto3xxGainToCurrentTable(float, Enum):
|
|
31
|
+
"""These are the voltage to current setting for Femto 3xx current amplifier"""
|
|
32
|
+
|
|
33
|
+
SEN_1 = 1e4
|
|
34
|
+
SEN_2 = 1e5
|
|
35
|
+
SEN_3 = 1e6
|
|
36
|
+
SEN_4 = 1e7
|
|
37
|
+
SEN_5 = 1e8
|
|
38
|
+
SEN_6 = 1e9
|
|
39
|
+
SEN_7 = 1e10
|
|
40
|
+
SEN_8 = 1e11
|
|
41
|
+
SEN_9 = 1e12
|
|
42
|
+
SEN_10 = 1e13
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Femto3xxRaiseTime(float, Enum):
|
|
46
|
+
"""These are the gain dependent raise time(s) for Femto 3xx current amplifier"""
|
|
47
|
+
|
|
48
|
+
SEN_1 = 0.8e-3
|
|
49
|
+
SEN_2 = 0.8e-3
|
|
50
|
+
SEN_3 = 0.8e-3
|
|
51
|
+
SEN_4 = 0.8e-3
|
|
52
|
+
SEN_5 = 2.3e-3
|
|
53
|
+
SEN_6 = 2.3e-3
|
|
54
|
+
SEN_7 = 17e-3
|
|
55
|
+
SEN_8 = 17e-3
|
|
56
|
+
SEN_9 = 350e-3
|
|
57
|
+
SEN_10 = 350e-3
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class FemtoDDPCA(CurrentAmp):
|
|
61
|
+
"""
|
|
62
|
+
Femto current amplifier device, this class should cover all DDPCA Femto current
|
|
63
|
+
amplifiers, As the main different between all the DDPCA Femto is their gain table
|
|
64
|
+
and response time table.
|
|
65
|
+
Attributes:
|
|
66
|
+
gain (SignalRW): This is the epic signal that control current amplifier gain.
|
|
67
|
+
gain_table (strictEnum): The table epic use to set gain.
|
|
68
|
+
upperlimit (float): upperlimit of the current amplifier
|
|
69
|
+
lowerlimit (float): lowerlimit of the current amplifier
|
|
70
|
+
timeout (float): Maximum waiting time in second for setting gain.
|
|
71
|
+
raise_timetable (Enum): Table contain the minimum amount of time to wait after
|
|
72
|
+
changing gain.
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
prefix: str,
|
|
79
|
+
suffix: str,
|
|
80
|
+
gain_table: type[StrictEnum],
|
|
81
|
+
gain_to_current_table: type[Enum],
|
|
82
|
+
raise_timetable: type[Enum],
|
|
83
|
+
upperlimit: float = 8.8,
|
|
84
|
+
lowerlimit: float = 0.68,
|
|
85
|
+
timeout: float = 1,
|
|
86
|
+
name: str = "",
|
|
87
|
+
) -> None:
|
|
88
|
+
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
89
|
+
self.gain = epics_signal_rw(gain_table, prefix + suffix)
|
|
90
|
+
|
|
91
|
+
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
92
|
+
self.gain_table = gain_table
|
|
93
|
+
self.timeout = timeout
|
|
94
|
+
self.raise_timetable = raise_timetable
|
|
95
|
+
self.upperlimit = upperlimit
|
|
96
|
+
self.lowerlimit = lowerlimit
|
|
97
|
+
super().__init__(name=name, gain_conversion_table=gain_to_current_table)
|
|
98
|
+
|
|
99
|
+
@AsyncStatus.wrap
|
|
100
|
+
async def set(self, value) -> None:
|
|
101
|
+
if value not in [item.value for item in self.gain_conversion_table]:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
f"Gain value {value} is not within {self.name} range."
|
|
104
|
+
+ "\n Available gain:"
|
|
105
|
+
+ f" {[f'{c.value:.0e}' for c in self.gain_conversion_table]}"
|
|
106
|
+
)
|
|
107
|
+
SEN_setting = self.gain_conversion_table(value).name
|
|
108
|
+
LOGGER.info(f"{self.name} gain change to {SEN_setting}:{value}")
|
|
109
|
+
|
|
110
|
+
await self.gain.set(
|
|
111
|
+
value=self.gain_table[SEN_setting].value,
|
|
112
|
+
timeout=self.timeout,
|
|
113
|
+
)
|
|
114
|
+
# wait for current amplifier's bandpass filter to settle.
|
|
115
|
+
await asyncio.sleep(self.raise_timetable[SEN_setting].value)
|
|
116
|
+
|
|
117
|
+
@AsyncStatus.wrap
|
|
118
|
+
async def increase_gain(self, value: int = 1) -> None:
|
|
119
|
+
current_gain = int((await self.get_gain()).name.split("_")[-1])
|
|
120
|
+
current_gain += value
|
|
121
|
+
if current_gain > len(self.gain_table):
|
|
122
|
+
raise ValueError("Gain at max value")
|
|
123
|
+
await self.set(self.gain_conversion_table[f"SEN_{current_gain}"])
|
|
124
|
+
|
|
125
|
+
@AsyncStatus.wrap
|
|
126
|
+
async def decrease_gain(self, value: int = 1) -> None:
|
|
127
|
+
current_gain = int((await self.get_gain()).name.split("_")[-1])
|
|
128
|
+
current_gain -= value
|
|
129
|
+
if current_gain < 1:
|
|
130
|
+
raise ValueError("Gain at min value")
|
|
131
|
+
await self.set(self.gain_conversion_table[f"SEN_{current_gain}"])
|
|
132
|
+
|
|
133
|
+
@AsyncStatus.wrap
|
|
134
|
+
async def get_gain(self) -> Enum:
|
|
135
|
+
return self.gain_conversion_table[(await self.gain.get_value()).name]
|
|
136
|
+
|
|
137
|
+
@AsyncStatus.wrap
|
|
138
|
+
async def get_upperlimit(self) -> float:
|
|
139
|
+
return self.upperlimit
|
|
140
|
+
|
|
141
|
+
@AsyncStatus.wrap
|
|
142
|
+
async def get_lowerlimit(self) -> float:
|
|
143
|
+
return self.lowerlimit
|