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,60 @@ 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
- def get_deadtime (self , _exposure : float ) -> float :
120
- return 0.001 # Picked from a hat
122
+ def get_deadtime (self , exposure : float ) -> float :
123
+ # Not found in technical specifications
124
+ # May need to be discovered experimentally
125
+ # Returning 0.001 as a safe non-zero default
126
+ return 0.001
121
127
122
128
async def arm (
123
129
self ,
124
- trigger : DetectorTrigger = DetectorTrigger . internal ,
125
- num : int = 0 ,
126
- exposure : Optional [ float ] = None ,
130
+ trigger : DetectorTrigger ,
131
+ num : int ,
132
+ exposure : float ,
127
133
) -> AsyncStatus :
128
- """Arms the tetramm.
134
+ """Arms the TetrAMM
135
+
136
+ Args:
137
+ trigger (DetectorTrigger): Trigger type: supports edge_trigger, constant_gate
138
+ num (int): ignored
139
+ exposure (float): Exposure time in seconds
129
140
130
- Note that num is meaningless in this context, and is ignored.
141
+ Raises:
142
+ ValueError: If DetectorTrigger is not supported
131
143
"""
132
- if exposure is None :
133
- raise ValueError ("Exposure time is required" )
134
144
if trigger not in {DetectorTrigger .edge_trigger , DetectorTrigger .constant_gate }:
135
145
raise ValueError ("Only edge triggers are supported" )
136
146
137
- # trigger mode must be set first and on it's own!
147
+ # trigger mode must be set first and on its own!
138
148
await self ._drv .trigger_mode .set (TetrammTrigger .ExtTrigger )
139
149
140
150
await asyncio .gather (
@@ -148,17 +158,31 @@ async def arm(
148
158
async def disarm (self ):
149
159
await stop_busy_record (self ._drv .acquire , 0 , timeout = 1 )
150
160
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 )
161
+ async def set_frame_time (self , frame_time : float ):
162
+ """Tries to set the exposure time of a single frame.
163
+
164
+ As during the exposure time, the device must collect an integer number
165
+ of readings, in the case where the frame_time is not a multiple of the base
166
+ sample rate, it will be lowered to the prior multiple ot ensure triggers
167
+ are not missed.
168
+
169
+ Args:
170
+ frame_time (float): The time for a single frame in seconds
171
+
172
+ Raises:
173
+ ValueError: If frame_time is too low to collect the required number
174
+ of readings per frame.
175
+ """
176
+
177
+ values_per_reading : int = int (
178
+ frame_time * self .base_sample_rate / self .readings_per_frame
158
179
)
180
+
159
181
if values_per_reading < self .minimum_values_per_reading :
160
182
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 } "
183
+ f"frame_time { frame_time } is too low to collect at least \
184
+ { self .minimum_values_per_reading } values per reading, at \
185
+ { self .readings_per_frame } readings per frame."
162
186
)
163
187
await self ._drv .values_per_reading .set (values_per_reading )
164
188
@@ -184,9 +208,6 @@ def minimum_frame_time(self, frame: float):
184
208
)
185
209
186
210
187
- # TODO: need to change this name.
188
- MAX_CHANNELS = 11
189
-
190
211
IDLE_TETRAMM = {
191
212
"drv.acquire" : 0 ,
192
213
}
@@ -230,13 +251,16 @@ def free_tetramm(dev: TetrammDriver) -> Generator[Msg, None, None]:
230
251
231
252
232
253
class TetrammShapeProvider (ShapeProvider ):
254
+ max_channels = 11
255
+
233
256
def __init__ (self , controller : TetrammController ) -> None :
234
257
self .controller = controller
235
258
236
259
async def __call__ (self ) -> Sequence [int ]:
237
- return [MAX_CHANNELS , self .controller .readings_per_frame ]
260
+ return [self . max_channels , self .controller .readings_per_frame ]
238
261
239
262
263
+ # TODO: Support MeanValue signals https://github.com/DiamondLightSource/dodal/issues/337
240
264
class TetrammDetector (StandardDetector ):
241
265
def __init__ (
242
266
self ,
@@ -245,31 +269,23 @@ def __init__(
245
269
name : str ,
246
270
** scalar_sigs : str ,
247
271
) -> 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 )
272
+ self .drv = TetrammDriver (prefix + "DRV:" )
273
+ self .hdf = NDFileHDF (prefix + "HDF5:" )
274
+ controller = TetrammController (self .drv )
263
275
super ().__init__ (
264
276
controller ,
265
277
HDFWriter (
266
- hdf ,
278
+ self . hdf ,
267
279
directory_provider ,
268
280
lambda : self .name ,
269
281
TetrammShapeProvider (controller ),
270
282
** scalar_sigs ,
271
283
),
272
- [drv .values_per_reading , drv .averaging_time , drv .sample_time ],
284
+ [
285
+ self .drv .values_per_reading ,
286
+ self .drv .averaging_time ,
287
+ self .drv .sample_time ,
288
+ ],
273
289
name ,
274
290
)
275
291
0 commit comments