1515# This file represents a dummy labscript device for purposes of testing BLACS
1616# and labscript. The device is a Intermediate Device, and can be attached to
1717# 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+ #
2020# 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
2222# easy to extend this is anyone needed further functionality.
2323
2424
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
2737import 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+
2844
2945class DummyIntermediateDevice (IntermediateDevice ):
3046
3147 description = 'Dummy IntermediateDevice'
32- clock_limit = 1e6
3348
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
3551 allowed_children = [DigitalOut , AnalogOut ]
3652
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'
3979 IntermediateDevice .__init__ (self , name , parent_device , ** kwargs )
4080
4181 def generate_code (self , hdf5_file ):
@@ -54,43 +94,172 @@ def generate_code(self, hdf5_file):
5494 device_dtype = np .int8
5595 elif isinstance (device , AnalogOut ):
5696 device_dtype = np .float64
57- dtypes .append ((device .name , device_dtype ))
97+ dtypes .append ((device .connection , device_dtype ))
5898
5999 # create dataset
60100 out_table = np .zeros (len (times ), dtype = dtypes )
61101 for device in self .child_devices :
62- out_table [device .name ][:] = device .raw_output
102+ out_table [device .connection ][:] = device .raw_output
63103
64104 group .create_dataset ('OUTPUTS' , compression = config .compression , data = out_table )
65105
66106
67- from blacs .device_base_class import DeviceTab
68- from blacs .tab_base_classes import Worker
69-
70107@BLACS_tab
71108class DummyIntermediateDeviceTab (DeviceTab ):
72109 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+ )
74188 self .primary_worker = "main_worker"
75189
190+ # Set the capabilities of this device
191+ self .supports_remote_value_check (False )
192+ self .supports_smart_programming (False )
193+
194+
76195class DummyIntermediateDeviceWorker (Worker ):
77196 def init (self ):
78197 pass
79198
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+
80208 def program_manual (self , front_panel_values ):
81- return front_panel_values
209+ return front_panel_values
82210
83211 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 ]))
85217
86- def transition_to_manual (self ,abort = False ):
218+ return final_values
219+
220+ def transition_to_manual (self , abort = False ):
87221 return True
88222
89223 def abort_transition_to_buffered (self ):
90224 return self .transition_to_manual (True )
91-
225+
92226 def abort_buffered (self ):
93227 return self .transition_to_manual (True )
94228
95229 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