1
1
import asyncio
2
2
from enum import Enum
3
3
from math import floor
4
- from typing import Generator , Optional , Sequence
4
+ from typing import Generator , Sequence
5
5
6
6
import bluesky .plan_stubs as bps
7
7
from bluesky .protocols import Hints
@@ -91,50 +91,57 @@ def __init__(
91
91
92
92
93
93
class TetrammController (DetectorControl ):
94
+ """Controller for a TetrAMM current monitor
95
+
96
+ Attributes:
97
+ base_sample_rate (int): Fixed in hardware
98
+
99
+ Args:
100
+ drv (TetrammDriver): A configured driver for the device
101
+ maximum_readings_per_frame (int): Maximum number of readings per frame: actual readings may be lower if higher frame rate is required
102
+ minimum_values_per_reading (int): Lower bound on the values that will be averaged to create a single reading
103
+ readings_per_frame (int): Actual number of readings per frame.
104
+
105
+ """
106
+
107
+ base_sample_rate : int = 100_000
108
+
94
109
def __init__ (
95
110
self ,
96
111
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 ,
112
+ minimum_values_per_reading : int = 5 ,
113
+ maximum_readings_per_frame : int = 1_000 ,
114
+ readings_per_frame : int = 1_000 ,
101
115
):
116
+ # TODO: Are any of these also fixed by hardware constraints?
102
117
self ._drv = drv
103
-
104
- self .base_sample_rate = base_sample_rate
105
- """base rate - fixed in hardware"""
106
-
107
118
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
-
113
119
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
-
116
120
self .readings_per_frame = readings_per_frame
117
- """The number of readings per frame"""
118
121
119
122
def get_deadtime (self , _exposure : float ) -> float :
120
123
return 0.001 # Picked from a hat
121
124
122
125
async def arm (
123
126
self ,
124
- trigger : DetectorTrigger = DetectorTrigger . internal ,
125
- num : int = 0 ,
126
- exposure : Optional [ float ] = None ,
127
+ trigger : DetectorTrigger ,
128
+ num : int ,
129
+ exposure : float ,
127
130
) -> AsyncStatus :
128
- """Arms the tetramm.
131
+ """Arms the TetrAMM
132
+
133
+ Args:
134
+ trigger (DetectorTrigger): Trigger type: supports edge_trigger, constant_gate
135
+ num (int): ignored
136
+ exposure (float): Exposure time in seconds
129
137
130
- Note that num is meaningless in this context, and is ignored.
138
+ Raises:
139
+ ValueError: If DetectorTrigger is not supported
131
140
"""
132
- if exposure is None :
133
- raise ValueError ("Exposure time is required" )
134
141
if trigger not in {DetectorTrigger .edge_trigger , DetectorTrigger .constant_gate }:
135
142
raise ValueError ("Only edge triggers are supported" )
136
143
137
- # trigger mode must be set first and on it's own!
144
+ # trigger mode must be set first and on its own!
138
145
await self ._drv .trigger_mode .set (TetrammTrigger .ExtTrigger )
139
146
140
147
await asyncio .gather (
@@ -148,17 +155,31 @@ async def arm(
148
155
async def disarm (self ):
149
156
await stop_busy_record (self ._drv .acquire , 0 , timeout = 1 )
150
157
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 )
158
+ async def set_frame_time (self , frame_time : float ):
159
+ """Tries to set the exposure time of a single frame.
160
+
161
+ As during the exposure time, the device must collect an integer number
162
+ of readings, in the case where the frame_time is not a multiple of the base
163
+ sample rate, it will be lowered to the prior multiple ot ensure triggers
164
+ are not missed.
165
+
166
+ Args:
167
+ frame_time (float): The time for a single frame in seconds
168
+
169
+ Raises:
170
+ ValueError: If frame_time is too low to collect the required number
171
+ of readings per frame.
172
+ """
173
+
174
+ values_per_reading : int = int (
175
+ frame_time * self .base_sample_rate / self .readings_per_frame
158
176
)
177
+
159
178
if values_per_reading < self .minimum_values_per_reading :
160
179
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 } "
180
+ f"frame_time { frame_time } is too low to collect at least \
181
+ { self .minimum_values_per_reading } values per reading, at \
182
+ { self .readings_per_frame } readings per frame."
162
183
)
163
184
await self ._drv .values_per_reading .set (values_per_reading )
164
185
@@ -184,9 +205,6 @@ def minimum_frame_time(self, frame: float):
184
205
)
185
206
186
207
187
- # TODO: need to change this name.
188
- MAX_CHANNELS = 11
189
-
190
208
IDLE_TETRAMM = {
191
209
"drv.acquire" : 0 ,
192
210
}
@@ -230,13 +248,16 @@ def free_tetramm(dev: TetrammDriver) -> Generator[Msg, None, None]:
230
248
231
249
232
250
class TetrammShapeProvider (ShapeProvider ):
251
+ max_channels = 11
252
+
233
253
def __init__ (self , controller : TetrammController ) -> None :
234
254
self .controller = controller
235
255
236
256
async def __call__ (self ) -> Sequence [int ]:
237
- return [MAX_CHANNELS , self .controller .readings_per_frame ]
257
+ return [self . max_channels , self .controller .readings_per_frame ]
238
258
239
259
260
+ # TODO: Support MeanValue signals https://github.com/DiamondLightSource/dodal/issues/337
240
261
class TetrammDetector (StandardDetector ):
241
262
def __init__ (
242
263
self ,
@@ -245,31 +266,23 @@ def __init__(
245
266
name : str ,
246
267
** scalar_sigs : str ,
247
268
) -> 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 )
269
+ self .drv = TetrammDriver (prefix + "DRV:" )
270
+ self .hdf = NDFileHDF (prefix + "HDF5:" )
271
+ controller = TetrammController (self .drv )
263
272
super ().__init__ (
264
273
controller ,
265
274
HDFWriter (
266
- hdf ,
275
+ self . hdf ,
267
276
directory_provider ,
268
277
lambda : self .name ,
269
278
TetrammShapeProvider (controller ),
270
279
** scalar_sigs ,
271
280
),
272
- [drv .values_per_reading , drv .averaging_time , drv .sample_time ],
281
+ [
282
+ self .drv .values_per_reading ,
283
+ self .drv .averaging_time ,
284
+ self .drv .sample_time ,
285
+ ],
273
286
name ,
274
287
)
275
288
0 commit comments