15
15
# This file represents a dummy labscript device for purposes of testing BLACS
16
16
# and labscript. The device is a Intermediate Device, and can be attached to
17
17
# a pseudoclock in labscript in order to test the pseudoclock behaviour
18
- # without needing a real Intermediate Device.
19
- #
18
+ # without needing a real Intermediate Device.
19
+ #
20
20
# You can attach an arbitrary number of outputs to this device, however we
21
- # currently only support outputs of type AnalogOut and DigitalOut. I would be
21
+ # currently only support outputs of type AnalogOut and DigitalOut. It would be
22
22
# easy to extend this is anyone needed further functionality.
23
23
24
24
25
- from labscript_devices import labscript_device , BLACS_tab , BLACS_worker
26
- from labscript import IntermediateDevice , DigitalOut , AnalogOut , config
25
+ from labscript_devices import (
26
+ BLACS_tab ,
27
+ runviewer_parser ,
28
+ )
29
+ from labscript import (
30
+ IntermediateDevice ,
31
+ DigitalOut ,
32
+ AnalogOut ,
33
+ config ,
34
+ set_passed_properties ,
35
+ )
36
+ from labscript_devices .NI_DAQmx .utils import split_conn_AO , split_conn_DO
27
37
import numpy as np
38
+ import labscript_utils .h5_lock # noqa: F401
39
+ import h5py
40
+
41
+ from blacs .device_base_class import DeviceTab
42
+ from blacs .tab_base_classes import Worker
43
+
28
44
29
45
class DummyIntermediateDevice (IntermediateDevice ):
30
46
31
47
description = 'Dummy IntermediateDevice'
32
- clock_limit = 1e6
33
48
34
- # If this is updated, then you need to update generate_code to support whatever types you add
49
+ # If this is updated, then you need to update generate_code to support whatever
50
+ # types you add
35
51
allowed_children = [DigitalOut , AnalogOut ]
36
52
37
- def __init__ (self , name , parent_device , BLACS_connection = 'dummy_connection' , ** kwargs ):
38
- self .BLACS_connection = BLACS_connection
53
+ @set_passed_properties (
54
+ property_names = {
55
+ "connection_table_properties" : [
56
+ "AO_range" ,
57
+ "num_AO" ,
58
+ "ports" ,
59
+ "clock_limit" ,
60
+ ],
61
+ "device_properties" : [],
62
+ }
63
+ )
64
+ def __init__ (
65
+ self ,
66
+ name ,
67
+ parent_device = None ,
68
+ AO_range = [- 10.0 , 10.0 ],
69
+ num_AO = 4 ,
70
+ ports = {'port0' : {'num_lines' : 32 , 'supports_buffered' : True }},
71
+ clock_limit = 1e6 ,
72
+ ** kwargs ,
73
+ ):
74
+ self .AO_range = AO_range
75
+ self .num_AO = num_AO
76
+ self .ports = ports if ports is not None else {}
77
+ self .clock_limit = clock_limit
78
+ self .BLACS_connection = 'dummy_connection'
39
79
IntermediateDevice .__init__ (self , name , parent_device , ** kwargs )
40
80
41
81
def generate_code (self , hdf5_file ):
@@ -54,43 +94,172 @@ def generate_code(self, hdf5_file):
54
94
device_dtype = np .int8
55
95
elif isinstance (device , AnalogOut ):
56
96
device_dtype = np .float64
57
- dtypes .append ((device .name , device_dtype ))
97
+ dtypes .append ((device .connection , device_dtype ))
58
98
59
99
# create dataset
60
100
out_table = np .zeros (len (times ), dtype = dtypes )
61
101
for device in self .child_devices :
62
- out_table [device .name ][:] = device .raw_output
102
+ out_table [device .connection ][:] = device .raw_output
63
103
64
104
group .create_dataset ('OUTPUTS' , compression = config .compression , data = out_table )
65
105
66
106
67
- from blacs .device_base_class import DeviceTab
68
- from blacs .tab_base_classes import Worker
69
-
70
107
@BLACS_tab
71
108
class DummyIntermediateDeviceTab (DeviceTab ):
72
109
def initialise_GUI (self ):
73
- self .create_worker ("main_worker" ,DummyIntermediateDeviceWorker ,{})
110
+ # Get capabilities from connection table properties:
111
+ connection_table = self .settings ['connection_table' ]
112
+ properties = connection_table .find_by_name (self .device_name ).properties
113
+
114
+ num_AO = properties ['num_AO' ]
115
+ # num_DO = properties['num_DO']
116
+ ports = properties ['ports' ]
117
+
118
+ AO_base_units = 'V'
119
+ if num_AO > 0 :
120
+ AO_base_min , AO_base_max = properties ['AO_range' ]
121
+ else :
122
+ AO_base_min , AO_base_max = None , None
123
+ AO_base_step = 0.1
124
+ AO_base_decimals = 3
125
+
126
+ # Create output objects:
127
+ AO_prop = {}
128
+ for i in range (num_AO ):
129
+ AO_prop ['ao%d' % i ] = {
130
+ 'base_unit' : AO_base_units ,
131
+ 'min' : AO_base_min ,
132
+ 'max' : AO_base_max ,
133
+ 'step' : AO_base_step ,
134
+ 'decimals' : AO_base_decimals ,
135
+ }
136
+
137
+ DO_proplist = []
138
+ DO_hardware_names = []
139
+ for port_num in range (len (ports )):
140
+ port_str = 'port%d' % port_num
141
+ port_props = {}
142
+ for line in range (ports [port_str ]['num_lines' ]):
143
+ hardware_name = 'port%d/line%d' % (port_num , line )
144
+ port_props [hardware_name ] = {}
145
+ DO_hardware_names .append (hardware_name )
146
+ DO_proplist .append ((port_str , port_props ))
147
+
148
+ # Create the output objects
149
+ self .create_analog_outputs (AO_prop )
150
+
151
+ # Create widgets for outputs defined so far (i.e. analog outputs only)
152
+ _ , AO_widgets , _ = self .auto_create_widgets ()
153
+
154
+ # now create the digital output objects one port at a time
155
+ for _ , DO_prop in DO_proplist :
156
+ self .create_digital_outputs (DO_prop )
157
+
158
+ # Manually create the digital output widgets so they are grouped separately
159
+ DO_widgets_by_port = {}
160
+ for port_str , DO_prop in DO_proplist :
161
+ DO_widgets_by_port [port_str ] = self .create_digital_widgets (DO_prop )
162
+
163
+ # Auto place the widgets in the UI, specifying sort keys for ordering them:
164
+ widget_list = [("Analog outputs" , AO_widgets , split_conn_AO )]
165
+ for port_num in range (len (ports )):
166
+ port_str = 'port%d' % port_num
167
+ DO_widgets = DO_widgets_by_port [port_str ]
168
+ name = "Digital outputs: %s" % port_str
169
+ if ports [port_str ]['supports_buffered' ]:
170
+ name += ' (buffered)'
171
+ else :
172
+ name += ' (static)'
173
+ widget_list .append ((name , DO_widgets , split_conn_DO ))
174
+ self .auto_place_widgets (* widget_list )
175
+
176
+ # Create and set the primary worker
177
+ self .create_worker (
178
+ "main_worker" ,
179
+ DummyIntermediateDeviceWorker ,
180
+ {
181
+ 'Vmin' : AO_base_min ,
182
+ 'Vmax' : AO_base_max ,
183
+ 'num_AO' : num_AO ,
184
+ 'ports' : ports ,
185
+ 'DO_hardware_names' : DO_hardware_names ,
186
+ },
187
+ )
74
188
self .primary_worker = "main_worker"
75
189
190
+ # Set the capabilities of this device
191
+ self .supports_remote_value_check (False )
192
+ self .supports_smart_programming (False )
193
+
194
+
76
195
class DummyIntermediateDeviceWorker (Worker ):
77
196
def init (self ):
78
197
pass
79
198
199
+ def get_output_table (self , h5file , device_name ):
200
+ """Return the OUTPUT table from the file, or None if it does not exist."""
201
+ with h5py .File (h5file , 'r' ) as hdf5_file :
202
+ group = hdf5_file ['devices' ][device_name ]
203
+ try :
204
+ return group ['OUTPUTS' ][:]
205
+ except KeyError :
206
+ return None
207
+
80
208
def program_manual (self , front_panel_values ):
81
- return front_panel_values
209
+ return front_panel_values
82
210
83
211
def transition_to_buffered (self , device_name , h5file , initial_values , fresh ):
84
- return initial_values
212
+ # Get the data to be programmed into the output tasks:
213
+ outputs = self .get_output_table (h5file , device_name )
214
+
215
+ # Collect the final values of the outputs
216
+ final_values = dict (zip (outputs .dtype .names , outputs [- 1 ]))
85
217
86
- def transition_to_manual (self ,abort = False ):
218
+ return final_values
219
+
220
+ def transition_to_manual (self , abort = False ):
87
221
return True
88
222
89
223
def abort_transition_to_buffered (self ):
90
224
return self .transition_to_manual (True )
91
-
225
+
92
226
def abort_buffered (self ):
93
227
return self .transition_to_manual (True )
94
228
95
229
def shutdown (self ):
96
- pass
230
+ pass
231
+
232
+
233
+ @runviewer_parser
234
+ class DummyIntermediateDeviceParser (object ):
235
+ def __init__ (self , path , device ):
236
+ self .path = path
237
+ self .name = device .name
238
+ self .device = device
239
+
240
+ def get_traces (self , add_trace , clock = None ):
241
+ times , clock_value = clock [0 ], clock [1 ]
242
+
243
+ clock_indices = np .where ((clock_value [1 :] - clock_value [:- 1 ]) == 1 )[0 ] + 1
244
+ # If initial clock value is 1, then this counts as a rising edge (clock should
245
+ # be 0 before experiment) but this is not picked up by the above code. So we
246
+ # insert it!
247
+ if clock_value [0 ] == 1 :
248
+ clock_indices = np .insert (clock_indices , 0 , 0 )
249
+ clock_ticks = times [clock_indices ]
250
+
251
+ # Get the output table from the experiment shot file
252
+ with h5py .File (self .path , 'r' ) as hdf5_file :
253
+ outputs = hdf5_file [f"devices/{ self .name } /OUTPUTS" ][:]
254
+
255
+ traces = {}
256
+
257
+ for channel in outputs .dtype .names :
258
+ traces [channel ] = (clock_ticks , outputs [channel ])
259
+
260
+ for channel_name , channel in self .device .child_list .items ():
261
+ if channel .parent_port in traces :
262
+ trace = traces [channel .parent_port ]
263
+ add_trace (channel_name , trace , self .name , channel .parent_port )
264
+
265
+ return {}
0 commit comments