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.
Files changed (43) hide show
  1. {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.0.dist-info}/METADATA +3 -3
  2. {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.0.dist-info}/RECORD +43 -29
  3. {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.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 +20 -3
  7. dodal/beamlines/i04.py +3 -3
  8. dodal/beamlines/i10.py +105 -0
  9. dodal/beamlines/i22.py +15 -0
  10. dodal/beamlines/i24.py +1 -1
  11. dodal/beamlines/p99.py +6 -2
  12. dodal/common/crystal_metadata.py +3 -3
  13. dodal/common/udc_directory_provider.py +3 -1
  14. dodal/devices/aperturescatterguard.py +3 -0
  15. dodal/devices/{attenuator.py → attenuator/attenuator.py} +29 -1
  16. dodal/devices/attenuator/filter.py +11 -0
  17. dodal/devices/attenuator/filter_selections.py +72 -0
  18. dodal/devices/bimorph_mirror.py +151 -0
  19. dodal/devices/current_amplifiers/__init__.py +34 -0
  20. dodal/devices/current_amplifiers/current_amplifier.py +103 -0
  21. dodal/devices/current_amplifiers/current_amplifier_detector.py +109 -0
  22. dodal/devices/current_amplifiers/femto.py +143 -0
  23. dodal/devices/current_amplifiers/sr570.py +214 -0
  24. dodal/devices/current_amplifiers/struck_scaler_counter.py +79 -0
  25. dodal/devices/detector/det_dim_constants.py +15 -0
  26. dodal/devices/eiger_odin.py +3 -3
  27. dodal/devices/fast_grid_scan.py +8 -3
  28. dodal/devices/i03/beamstop.py +85 -0
  29. dodal/devices/i04/transfocator.py +67 -53
  30. dodal/devices/i10/rasor/rasor_current_amp.py +72 -0
  31. dodal/devices/i10/rasor/rasor_motors.py +62 -0
  32. dodal/devices/i10/rasor/rasor_scaler_cards.py +12 -0
  33. dodal/devices/p99/sample_stage.py +2 -28
  34. dodal/devices/robot.py +2 -2
  35. dodal/devices/undulator_dcm.py +9 -11
  36. dodal/devices/zebra.py +6 -1
  37. dodal/devices/zocalo/zocalo_interaction.py +2 -1
  38. dodal/devices/zocalo/zocalo_results.py +22 -2
  39. dodal/log.py +2 -2
  40. dodal/plans/wrapped.py +3 -3
  41. {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.0.dist-info}/LICENSE +0 -0
  42. {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.0.dist-info}/entry_points.txt +0 -0
  43. {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