Skip to content

Commit 896212a

Browse files
committed
Address review comments
1 parent 3bd44f4 commit 896212a

File tree

6 files changed

+143
-94
lines changed

6 files changed

+143
-94
lines changed

src/dodal/beamlines/i22.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ def waxs(
3939
)
4040

4141

42-
def panda(wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False) -> PandA:
42+
def panda_01(
43+
wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
44+
) -> PandA:
4345
return device_instantiation(
4446
PandA, "panda-01", "-EA-PANDA-01:", wait_for_connection, fake_with_ophyd_sim
4547
)
@@ -53,12 +55,12 @@ def linkam(
5355
)
5456

5557

56-
def tetramm1(
58+
def incident_beam(
5759
wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
5860
) -> TetrammDetector:
5961
return device_instantiation(
6062
TetrammDetector,
61-
"i0",
63+
"incident_beam",
6264
"-EA-XBPM-02:",
6365
wait_for_connection,
6466
fake_with_ophyd_sim,
@@ -67,12 +69,12 @@ def tetramm1(
6769
)
6870

6971

70-
def tetramm2(
72+
def transmitted_beam(
7173
wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
7274
) -> TetrammDetector:
7375
return device_instantiation(
7476
TetrammDetector,
75-
"it",
77+
"transmitted_beam",
7678
"-EA-TTRM-02:",
7779
wait_for_connection,
7880
fake_with_ophyd_sim,

src/dodal/beamlines/p38.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ def d12(
3939
)
4040

4141

42-
def linkam3(
42+
def linkam(
4343
wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
4444
) -> Linkam3:
4545
return device_instantiation(
46-
Linkam3, "linkam3", "-EA-LINKM-02:", wait_for_connection, fake_with_ophyd_sim
46+
Linkam3, "linkam", "-EA-LINKM-02:", wait_for_connection, fake_with_ophyd_sim
4747
)
4848

4949

@@ -60,7 +60,9 @@ def tetramm(
6060
)
6161

6262

63-
def panda(wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False) -> PandA:
63+
def panda_01(
64+
wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
65+
) -> PandA:
6466
return device_instantiation(
6567
PandA, "panda", "-EA-PANDA-01:", wait_for_connection, fake_with_ophyd_sim
6668
)

src/dodal/devices/areadetector/adaravis.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,10 @@ def __init__(self, driver: AdAravisMakoDriver, gpio_number: int) -> None:
154154
}
155155

156156
def get_deadtime(self, exposure: float) -> float:
157-
return 0.002
157+
# Not found in technical specifications
158+
# May need to be discovered experimentally
159+
# Returning 0.001 as a safe non-zero default
160+
return 0.001
158161

159162
async def arm(
160163
self,

src/dodal/devices/linkam3.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,19 @@ class PumpControl(str, Enum):
1414

1515

1616
class Linkam3(StandardReadable):
17-
tolerance: float = 0.5
18-
"""
19-
The deadband around the setpoint within which the position is assumed to
20-
have been reached
17+
"""Device to represent a Linkam3 temperature controller
18+
19+
Attributes:
20+
tolerance (float): Deadband around the setpoint within which the position is assumed to have been reached
21+
settle_time (int): The delay between reaching the setpoint and the move being considered complete
22+
23+
Args:
24+
prefix (str): PV prefix for this device
25+
name (str): unique name for this device
2126
"""
2227

28+
tolerance: float = 0.5
2329
settle_time: int = 0
24-
"""The delay between reaching the setpoint and the move being considered complete"""
2530

2631
def __init__(self, prefix: str, name: str):
2732
self.temp = epics_signal_r(float, prefix + "TEMP:")
@@ -80,21 +85,25 @@ def set(self, new_position: float, timeout: Optional[float] = None) -> AsyncStat
8085
coro = asyncio.wait_for(self._move(new_position, watchers), timeout=timeout)
8186
return AsyncStatus(coro, watchers)
8287

83-
# TODO: Check bitshift order
88+
# TODO: Make use of values in Status.
89+
# https://github.com/DiamondLightSource/dodal/issues/338
90+
async def _is_nth_bit_set(self, n: int) -> bool:
91+
return bool(int(await self.status.get_value()) & 1 << n)
92+
8493
async def in_error(self) -> bool:
85-
return bool(int(await self.status.get_value()) & 1)
94+
return await self._is_nth_bit_set(0)
8695

8796
async def at_setpoint(self) -> bool:
88-
return bool(int(await self.status.get_value()) & 1 << 1)
97+
return await self._is_nth_bit_set(1)
8998

9099
async def heater_on(self) -> bool:
91-
return bool(int(await self.status.get_value()) & 1 << 2)
100+
return await self._is_nth_bit_set(2)
92101

93102
async def pump_on(self) -> bool:
94-
return bool(int(await self.status.get_value()) & 1 << 3)
103+
return await self._is_nth_bit_set(3)
95104

96105
async def pump_auto(self) -> bool:
97-
return bool(int(await self.status.get_value()) & 1 << 4)
106+
return await self._is_nth_bit_set(4)
98107

99108
async def locate(self) -> Location:
100109
return {

src/dodal/devices/tetramm.py

Lines changed: 77 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import asyncio
22
from enum import Enum
3-
from math import floor
4-
from typing import Generator, Optional, Sequence
3+
from typing import Generator, Sequence
54

65
import bluesky.plan_stubs as bps
76
from bluesky.protocols import Hints
@@ -91,50 +90,60 @@ def __init__(
9190

9291

9392
class TetrammController(DetectorControl):
93+
"""Controller for a TetrAMM current monitor
94+
95+
Attributes:
96+
base_sample_rate (int): Fixed in hardware
97+
98+
Args:
99+
drv (TetrammDriver): A configured driver for the device
100+
maximum_readings_per_frame (int): Maximum number of readings per frame: actual readings may be lower if higher frame rate is required
101+
minimum_values_per_reading (int): Lower bound on the values that will be averaged to create a single reading
102+
readings_per_frame (int): Actual number of readings per frame.
103+
104+
"""
105+
106+
base_sample_rate: int = 100_000
107+
94108
def __init__(
95109
self,
96110
drv: TetrammDriver,
97-
minimum_values_per_reading=5,
98-
maximum_readings_per_frame=1_000,
99-
base_sample_rate=100_000,
100-
readings_per_frame=1_000,
111+
minimum_values_per_reading: int = 5,
112+
maximum_readings_per_frame: int = 1_000,
113+
readings_per_frame: int = 1_000,
101114
):
115+
# TODO: Are any of these also fixed by hardware constraints?
102116
self._drv = drv
103-
104-
self.base_sample_rate = base_sample_rate
105-
"""base rate - fixed in hardware"""
106-
107117
self.maximum_readings_per_frame = maximum_readings_per_frame
108-
"""
109-
Maximum number of readings per frame
110-
Actual readings may be lower if higher frame rate is required
111-
"""
112-
113118
self.minimum_values_per_reading = minimum_values_per_reading
114-
"""A lower bound on the values that will be averaged to create a single reading"""
115-
116119
self.readings_per_frame = readings_per_frame
117-
"""The number of readings per frame"""
118120

119-
def get_deadtime(self, _exposure: float) -> float:
120-
return 0.001 # Picked from a hat
121+
def get_deadtime(self, exposure: float) -> float:
122+
# Not found in technical specifications
123+
# May need to be discovered experimentally
124+
# Returning 0.001 as a safe non-zero default
125+
return 0.001
121126

122127
async def arm(
123128
self,
124-
trigger: DetectorTrigger = DetectorTrigger.internal,
125-
num: int = 0,
126-
exposure: Optional[float] = None,
129+
trigger: DetectorTrigger,
130+
num: int,
131+
exposure: float,
127132
) -> AsyncStatus:
128-
"""Arms the tetramm.
133+
"""Arms the TetrAMM
134+
135+
Args:
136+
trigger (DetectorTrigger): Trigger type: supports edge_trigger, constant_gate
137+
num (int): ignored
138+
exposure (float): Exposure time in seconds
129139
130-
Note that num is meaningless in this context, and is ignored.
140+
Raises:
141+
ValueError: If DetectorTrigger is not supported
131142
"""
132-
if exposure is None:
133-
raise ValueError("Exposure time is required")
134143
if trigger not in {DetectorTrigger.edge_trigger, DetectorTrigger.constant_gate}:
135144
raise ValueError("Only edge triggers are supported")
136145

137-
# trigger mode must be set first and on it's own!
146+
# trigger mode must be set first and on its own!
138147
await self._drv.trigger_mode.set(TetrammTrigger.ExtTrigger)
139148

140149
await asyncio.gather(
@@ -148,17 +157,31 @@ async def arm(
148157
async def disarm(self):
149158
await stop_busy_record(self._drv.acquire, 0, timeout=1)
150159

151-
async def set_frame_time(self, seconds):
152-
# It may not always be possible to set the exact collection time if the
153-
# exposure is not a multiple of the base sample rate. In this case it
154-
# will always be the closest collection time *below* the requested time
155-
# to ensure that triggers are not missed.
156-
values_per_reading = int(
157-
floor(seconds * self.base_sample_rate / self.readings_per_frame)
160+
async def set_frame_time(self, frame_time: float):
161+
"""Tries to set the exposure time of a single frame.
162+
163+
As during the exposure time, the device must collect an integer number
164+
of readings, in the case where the frame_time is not a multiple of the base
165+
sample rate, it will be lowered to the prior multiple ot ensure triggers
166+
are not missed.
167+
168+
Args:
169+
frame_time (float): The time for a single frame in seconds
170+
171+
Raises:
172+
ValueError: If frame_time is too low to collect the required number
173+
of readings per frame.
174+
"""
175+
176+
values_per_reading: int = int(
177+
frame_time * self.base_sample_rate / self.readings_per_frame
158178
)
179+
159180
if values_per_reading < self.minimum_values_per_reading:
160181
raise ValueError(
161-
f"Exposure ({seconds}) too short to collect required number of readings {self.readings_per_frame}. Values per reading is {values_per_reading}, seconds is : {seconds}"
182+
f"frame_time {frame_time} is too low to collect at least \
183+
{self.minimum_values_per_reading} values per reading, at \
184+
{self.readings_per_frame} readings per frame."
162185
)
163186
await self._drv.values_per_reading.set(values_per_reading)
164187

@@ -173,20 +196,17 @@ def max_frame_rate(self, mfr: float):
173196

174197
@property
175198
def minimum_frame_time(self) -> float:
176-
sample_time = self.minimum_values_per_reading / self.base_sample_rate
177-
return self.readings_per_frame * sample_time
199+
time_per_reading = self.minimum_values_per_reading / self.base_sample_rate
200+
return self.readings_per_frame * time_per_reading
178201

179202
@minimum_frame_time.setter
180203
def minimum_frame_time(self, frame: float):
181-
sample_time = self.minimum_values_per_reading / self.base_sample_rate
182-
self.readings_per_frame = min(
183-
self.maximum_readings_per_frame, int(floor(frame / sample_time))
204+
time_per_reading = self.minimum_values_per_reading / self.base_sample_rate
205+
self.readings_per_frame = int(
206+
min(self.maximum_readings_per_frame, frame / time_per_reading)
184207
)
185208

186209

187-
# TODO: need to change this name.
188-
MAX_CHANNELS = 11
189-
190210
IDLE_TETRAMM = {
191211
"drv.acquire": 0,
192212
}
@@ -230,13 +250,16 @@ def free_tetramm(dev: TetrammDriver) -> Generator[Msg, None, None]:
230250

231251

232252
class TetrammShapeProvider(ShapeProvider):
253+
max_channels = 11
254+
233255
def __init__(self, controller: TetrammController) -> None:
234256
self.controller = controller
235257

236258
async def __call__(self) -> Sequence[int]:
237-
return [MAX_CHANNELS, self.controller.readings_per_frame]
259+
return [self.max_channels, self.controller.readings_per_frame]
238260

239261

262+
# TODO: Support MeanValue signals https://github.com/DiamondLightSource/dodal/issues/337
240263
class TetrammDetector(StandardDetector):
241264
def __init__(
242265
self,
@@ -245,31 +268,23 @@ def __init__(
245268
name: str,
246269
**scalar_sigs: str,
247270
) -> None:
248-
drv = TetrammDriver(prefix + "DRV:")
249-
hdf = NDFileHDF(prefix + "HDF5:")
250-
251-
self.drv = drv
252-
self.hdf = hdf
253-
254-
# TODO: how to make the below, readable signals?
255-
# self.current_1 = ad_r(float, prefix + ":Cur1:MeanValue")
256-
# self.current_2 = ad_r(float, prefix + ":Cur2:MeanValue")
257-
# self.current_3 = ad_r(float, prefix + ":Cur3:MeanValue")
258-
# self.current_4 = ad_r(float, prefix + ":Cur4:MeanValue")
259-
260-
# self.position_x = ad_r(float, prefix + ":PosX:MeanValue")
261-
# self.position_y = ad_r(float, prefix + ":PosY:MeanValue")
262-
controller = TetrammController(drv)
271+
self.drv = TetrammDriver(prefix + "DRV:")
272+
self.hdf = NDFileHDF(prefix + "HDF5:")
273+
controller = TetrammController(self.drv)
263274
super().__init__(
264275
controller,
265276
HDFWriter(
266-
hdf,
277+
self.hdf,
267278
directory_provider,
268279
lambda: self.name,
269280
TetrammShapeProvider(controller),
270281
**scalar_sigs,
271282
),
272-
[drv.values_per_reading, drv.averaging_time, drv.sample_time],
283+
[
284+
self.drv.values_per_reading,
285+
self.drv.averaging_time,
286+
self.drv.sample_time,
287+
],
273288
name,
274289
)
275290

0 commit comments

Comments
 (0)