1
1
import asyncio
2
2
from enum import Enum
3
- from math import floor
4
- from typing import Generator , Optional , Sequence
3
+ from typing import Generator , Sequence
5
4
6
5
import bluesky .plan_stubs as bps
7
6
from bluesky .protocols import Hints
@@ -91,50 +90,60 @@ def __init__(
91
90
92
91
93
92
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
+
94
108
def __init__ (
95
109
self ,
96
110
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 ,
101
114
):
115
+ # TODO: Are any of these also fixed by hardware constraints?
102
116
self ._drv = drv
103
-
104
- self .base_sample_rate = base_sample_rate
105
- """base rate - fixed in hardware"""
106
-
107
117
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
118
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
119
self .readings_per_frame = readings_per_frame
117
- """The number of readings per frame"""
118
120
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
121
126
122
127
async def arm (
123
128
self ,
124
- trigger : DetectorTrigger = DetectorTrigger . internal ,
125
- num : int = 0 ,
126
- exposure : Optional [ float ] = None ,
129
+ trigger : DetectorTrigger ,
130
+ num : int ,
131
+ exposure : float ,
127
132
) -> 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
129
139
130
- Note that num is meaningless in this context, and is ignored.
140
+ Raises:
141
+ ValueError: If DetectorTrigger is not supported
131
142
"""
132
- if exposure is None :
133
- raise ValueError ("Exposure time is required" )
134
143
if trigger not in {DetectorTrigger .edge_trigger , DetectorTrigger .constant_gate }:
135
144
raise ValueError ("Only edge triggers are supported" )
136
145
137
- # trigger mode must be set first and on it's own!
146
+ # trigger mode must be set first and on its own!
138
147
await self ._drv .trigger_mode .set (TetrammTrigger .ExtTrigger )
139
148
140
149
await asyncio .gather (
@@ -148,17 +157,31 @@ async def arm(
148
157
async def disarm (self ):
149
158
await stop_busy_record (self ._drv .acquire , 0 , timeout = 1 )
150
159
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
158
178
)
179
+
159
180
if values_per_reading < self .minimum_values_per_reading :
160
181
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."
162
185
)
163
186
await self ._drv .values_per_reading .set (values_per_reading )
164
187
@@ -173,20 +196,17 @@ def max_frame_rate(self, mfr: float):
173
196
174
197
@property
175
198
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
178
201
179
202
@minimum_frame_time .setter
180
203
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 )
184
207
)
185
208
186
209
187
- # TODO: need to change this name.
188
- MAX_CHANNELS = 11
189
-
190
210
IDLE_TETRAMM = {
191
211
"drv.acquire" : 0 ,
192
212
}
@@ -230,13 +250,16 @@ def free_tetramm(dev: TetrammDriver) -> Generator[Msg, None, None]:
230
250
231
251
232
252
class TetrammShapeProvider (ShapeProvider ):
253
+ max_channels = 11
254
+
233
255
def __init__ (self , controller : TetrammController ) -> None :
234
256
self .controller = controller
235
257
236
258
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 ]
238
260
239
261
262
+ # TODO: Support MeanValue signals https://github.com/DiamondLightSource/dodal/issues/337
240
263
class TetrammDetector (StandardDetector ):
241
264
def __init__ (
242
265
self ,
@@ -245,31 +268,23 @@ def __init__(
245
268
name : str ,
246
269
** scalar_sigs : str ,
247
270
) -> 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 )
263
274
super ().__init__ (
264
275
controller ,
265
276
HDFWriter (
266
- hdf ,
277
+ self . hdf ,
267
278
directory_provider ,
268
279
lambda : self .name ,
269
280
TetrammShapeProvider (controller ),
270
281
** scalar_sigs ,
271
282
),
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
+ ],
273
288
name ,
274
289
)
275
290
0 commit comments