Skip to content

Commit dc437b5

Browse files
committed
Initialise with AnalogOut and DigitalOuts + add runviewer_parser
1 parent 55b1bd8 commit dc437b5

File tree

1 file changed

+189
-20
lines changed

1 file changed

+189
-20
lines changed

labscript_devices/DummyIntermediateDevice.py

Lines changed: 189 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,67 @@
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
2737
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+
2844

2945
class 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
71108
class 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+
76195
class 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

Comments
 (0)