Skip to content

Commit 20c2b6d

Browse files
committed
Improve NI-DAQmx docs a little.
1 parent 34751ce commit 20c2b6d

File tree

4 files changed

+204
-20
lines changed

4 files changed

+204
-20
lines changed

docs/source/devices/ni_daqs.rst

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@ The python bindings are provided by the PyDAQmx package, available through pip.
1717
Adding a Device
1818
~~~~~~~~~~~~~~~
1919

20-
While the `NI_DAQmx` device can be used directly by manually specifying the many necessary parameters, it is preferable to add the device via an appropriate subclass. This process is greatly simplified by using the `get_capabilities.py` script.
20+
While the `NI_DAQmx` device can be used directly by manually specifying the many necessary parameters,
21+
it is preferable to add the device via an appropriate subclass.
22+
This process is greatly simplified by using the :mod:`get_capabilities.py <labscript_devices.NI_DAQmx.models.get_capabilities>` script
23+
followed by the :mod:`generate_subclasses.py <labscript_devices.NI_DAQmx.models.generate_subclasses>` script.
2124

2225
To add support for a DAQmx device that is not yet supported, run `get_capabilities.py` on
2326
a computer with the device in question connected (or with a simulated device of the
2427
correct model configured in NI-MAX). This will introspect the capabilities of the device
25-
and add those details to capabilities.json. To generate labscript device classes for all
26-
devices whose capabilities are known, run `generate_classes.py`. Subclasses of NI_DAQmx
28+
and add those details to `capabilities.json`. To generate labscript device classes for all
29+
devices whose capabilities are known, run `generate_subclasses.py`. Subclasses of NI_DAQmx
2730
will be made in the `models` subfolder, and they can then be imported into labscript code with:
2831

29-
..code-block:: python
32+
.. code-block:: python
3033
3134
from labscript_devices.NI_DAQmx.labscript_devices import NI_PCIe_6363
3235

labscript_devices/NI_DAQmx/labscript_devices.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,52 @@ def __init__(
106106
supports_semiperiod_measurement=False,
107107
**kwargs
108108
):
109-
"""Generic class for NI_DAQmx devices."""
109+
"""Generic class for NI_DAQmx devices.
110+
111+
Generally over-ridden by device-specific subclasses that contain
112+
the introspected default values.
113+
114+
Args:
115+
name (str): name to assign to the created labscript device
116+
parent_device (clockline): Parent clockline device that will
117+
clock the outputs of this device
118+
clock_terminal (str): What input on the DAQ is used for the clockline
119+
MAX_name (str): NI-MAX device name
120+
static_AO (int, optional): Number of static analog output channels.
121+
static_DO (int, optional): Number of static digital output channels.
122+
clock_mirror_terminal (str, optional): Channel string of digital output
123+
that mirrors the input clock. Useful for daisy-chaning DAQs on the same
124+
clockline.
125+
acquisiton_rate (float, optional): Default sample rate of inputs.
126+
AI_range (iterable, optional): A `[Vmin, Vmax]` pair that sets the analog
127+
input voltage range for all analog inputs.
128+
AI_start_delay (float, optional): Time in seconds between start of an
129+
analog input task starting and the first sample.
130+
AO_range (iterable, optional): A `[Vmin, Vmax]` pair that sets the analog
131+
output voltage range for all analog outputs.
132+
max_AI_multi_chan_rate (float, optional): Max supported analog input
133+
sampling rate when using multiple channels.
134+
max_AI_single_chan_rate (float, optional): Max supported analog input
135+
sampling rate when only using a single channel.
136+
max_AO_sample_rate (float, optional): Max supported analog output
137+
sample rate.
138+
max_DO_sample_rate (float, optional): Max supported digital output
139+
sample rate.
140+
min_sermiperiod_measurement (float, optional): Minimum measurable time
141+
for a semiperiod measurement.
142+
num_AI (int, optional): Number of analog inputs channels.
143+
num_AO (int, optional): Number of analog output channels.
144+
num_CI (int, optional): Number of counter input channels.
145+
ports (dict, optional): Dictionarly of DIO ports, which number of lines
146+
and whether port supports buffered output.
147+
supports_buffered_AO (bool, optional): True if analog outputs support
148+
buffered output
149+
supports_buffered_DO (bool, optional): True if digital outputs support
150+
buffered output
151+
supports_semiperiod_measurement (bool, optional): True if deviec supports
152+
semi-period measurements
153+
154+
"""
110155

111156
# Default static output setting based on whether the device supports buffered
112157
# output:
@@ -169,8 +214,8 @@ def __init__(
169214

170215
self.wait_monitor_minimum_pulse_width = self.min_semiperiod_measurement
171216

172-
# Set allowed children based on capabilities:
173217
self.allowed_children = []
218+
'''Sets the allowed children types based on the capabilites.'''
174219
if self.num_AI > 0:
175220
self.allowed_children += [AnalogIn]
176221
if self.num_AO > 0 and static_AO:
@@ -198,7 +243,13 @@ def __init__(
198243
IntermediateDevice.__init__(self, name, parent_device, **kwargs)
199244

200245
def add_device(self, device):
201-
"""Error checking for adding a child device"""
246+
"""Error checking for adding a child device.
247+
248+
Args:
249+
device (labscript device): Child labscript device to
250+
attach to this device. Only types of devices in :obj:`allowed_children`
251+
can be attached.
252+
"""
202253
# Verify static/dynamic outputs compatible with configuration:
203254
if isinstance(device, StaticAnalogOut) and not self.static_AO:
204255
msg = """Cannot add StaticAnalogOut to NI_DAQmx device configured for
@@ -293,6 +344,7 @@ def _check_bounds(self, analogs):
293344
np.clip(output.raw_output, vmin, vmax, out=output.raw_output)
294345

295346
def _check_AI_not_too_fast(self, AI_table):
347+
"""Check that analog input acquisition rates do not exceed maximums."""
296348
if AI_table is None:
297349
return
298350
n = len(set(AI_table['connection']))
@@ -442,6 +494,14 @@ def _check_wait_monitor_timeout_device_config(self):
442494
raise RuntimeError(dedent(msg))
443495

444496
def generate_code(self, hdf5_file):
497+
"""Generates the hardware code from the script and saves it to the
498+
shot h5 file.
499+
500+
This is called automatically when a shot is compiled.
501+
502+
Args:
503+
hdf5_file (str): Path to shot's hdf5 file to save the instructions to.
504+
"""
445505
IntermediateDevice.generate_code(self, hdf5_file)
446506
analogs = {}
447507
digitals = {}

labscript_devices/NI_DAQmx/models/generate_subclasses.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@
1010
# file in the root of the project for the full license. #
1111
# #
1212
#####################################################################
13+
"""Reads the capabilities file and generates labscript devices
14+
for each known model of DAQ.
15+
16+
Called from the command line via
17+
18+
.. code-block:: shell
19+
20+
python generate_subclasses.py
21+
22+
"""
1323
import os
1424
import warnings
1525
import json
@@ -23,7 +33,12 @@
2333

2434

2535
def reformat_files(filepaths):
26-
"""Apply black formatter to a list of source files"""
36+
"""Apply `black <https://black.readthedocs.io/en/stable/>`_
37+
formatter to a list of source files.
38+
39+
Args:
40+
filepaths (list): List of python source files to format.
41+
"""
2742
try:
2843
import black
2944
except ImportError:
@@ -42,6 +57,11 @@ def reformat_files(filepaths):
4257

4358

4459
def main():
60+
"""Called when the script is run.
61+
62+
Will attempt to reformat the generated files using
63+
:func:`reformat_files`.
64+
"""
4565
capabilities = {}
4666
if os.path.exists(CAPABILITIES_FILE):
4767
with open(CAPABILITIES_FILE) as f:

labscript_devices/NI_DAQmx/models/get_capabilities.py

Lines changed: 113 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@
1010
# file in the root of the project for the full license. #
1111
# #
1212
#####################################################################
13+
"""This is a script to update `model_capabilities.json` with the capabilities of all
14+
NI-DAQmx devices currently connected to this computer.
15+
16+
Run this script to add support for a new model of NI-DAQmx device.
17+
Note that this will work with a simulated device configured through NI-MAX as well,
18+
so support can be added without actually having the physical device.
19+
20+
Called from the command line via
21+
22+
.. code-block:: shell
23+
24+
python get_capabilities.py
25+
26+
"""
1327

1428
import numpy as np
1529
import os
@@ -24,14 +38,15 @@
2438
CAPABILITIES_FILE = os.path.join(THIS_FOLDER, 'capabilities.json')
2539

2640

27-
"""This is a script to update model_capabilities.json with the capabilities of all
28-
NI-DAQmx devices currently connected to this computer. Run this script to add support
29-
for a new model of NI-DAQmx device. Note that this will work with a simulated device
30-
configured through NI-MAX as well, so support can be added without actually having the
31-
physical device"""
41+
def string_prop(func):
42+
"""String property wrapper.
3243
44+
Args:
45+
func (function): PyDAQmx library function that returns a string.
3346
34-
def string_prop(func):
47+
Returns:
48+
function: The wrapped function.
49+
"""
3550
def wrapped(name=None):
3651
BUFSIZE = 4096
3752
result = ctypes.create_string_buffer(BUFSIZE)
@@ -45,6 +60,14 @@ def wrapped(name=None):
4560

4661

4762
def bool_prop(func):
63+
"""Bool property wrapper.
64+
65+
Args:
66+
func (function): PyDAQmx library function that returns a boolean.
67+
68+
Returns:
69+
function: The wrapped function.
70+
"""
4871
def wrapped(name):
4972
result = bool32()
5073
func(name, byref(result))
@@ -54,6 +77,14 @@ def wrapped(name):
5477

5578

5679
def int32_prop(func):
80+
"""Int32 property wrapper.
81+
82+
Args:
83+
func (function): PyDAQmx library function that returns a int32.
84+
85+
Returns:
86+
function: The wrapped function.
87+
"""
5788
def wrapped(name):
5889
result = int32()
5990
func(name, byref(result))
@@ -63,6 +94,14 @@ def wrapped(name):
6394

6495

6596
def float64_prop(func):
97+
"""Float property wrapper.
98+
99+
Args:
100+
func (function): PyDAQmx library function that returns a float64.
101+
102+
Returns:
103+
function: The wrapped function.
104+
"""
66105
def wrapped(name):
67106
result = float64()
68107
func(name, byref(result))
@@ -72,6 +111,15 @@ def wrapped(name):
72111

73112

74113
def float64_array_prop(func):
114+
"""Array of floats property wrapper.
115+
116+
Args:
117+
func (function): PyDAQmx library function that returns an array of
118+
float64s.
119+
120+
Returns:
121+
function: The wrapped function.
122+
"""
75123
def wrapped(name):
76124
import warnings
77125

@@ -91,8 +139,15 @@ def wrapped(name):
91139

92140

93141
def chans(func):
94-
"""string_prop but splitting the return value into separate channels and stripping
95-
the device name from them"""
142+
"""string_prop but splitting the return value into separate channels
143+
and stripping the device name from them
144+
145+
Args:
146+
func (function): PyDAQmx library function that returns channel string.
147+
148+
Returns:
149+
function: The wrapped function.
150+
"""
96151
wrapped1 = string_prop(func)
97152

98153
def wrapped2(name):
@@ -126,6 +181,16 @@ def wrapped2(name):
126181

127182

128183
def port_supports_buffered(device_name, port, clock_terminal=None):
184+
"""Empirically determines if the digital port supports buffered output.
185+
186+
Args:
187+
device_name (str): NI-MAX device name
188+
port (int): Which port to intro-spect
189+
clock_terminal (str, optional): String that specifies the clock terminal.
190+
191+
Returns:
192+
bool: True if `port` supports buffered output.
193+
"""
129194
all_terminals = DAQmxGetDevTerminals(device_name)
130195
if clock_terminal is None:
131196
clock_terminal = all_terminals[0]
@@ -170,6 +235,15 @@ def port_supports_buffered(device_name, port, clock_terminal=None):
170235

171236

172237
def AI_start_delay(device_name):
238+
"""Empirically determines the analog inputs' start delay.
239+
240+
Args:
241+
device_name (str): NI-MAX device name
242+
243+
Returns:
244+
float: Analog input start delay in seconds. `None` if
245+
analog inputs not supported.
246+
"""
173247
if 'PFI0' not in DAQmxGetDevTerminals(device_name):
174248
return None
175249
task = Task()
@@ -202,9 +276,19 @@ def AI_start_delay(device_name):
202276

203277

204278
def supported_AI_ranges_for_non_differential_input(device_name, AI_ranges):
205-
"""Try AI ranges to see which are actually allowed for non-differential input, since
279+
"""Empirically determine the analog input voltage ranges for non-differential inputs.
280+
281+
Tries AI ranges to see which are actually allowed for non-differential input, since
206282
the largest range may only be available for differential input, which we don't
207-
attempt to support (though we could with a little effort)"""
283+
attempt to support (though we could with a little effort).
284+
285+
Args:
286+
device_name (str): NI-MAX device name
287+
AI_ranges (list): list of `[Vmin, Vmax]` pairs to check compatibility.
288+
289+
Returns:
290+
list: List of lists with the supported voltage ranges.
291+
"""
208292
chan = device_name + '/ai0'
209293
supported_ranges = []
210294
for Vmin, Vmax in AI_ranges:
@@ -227,6 +311,14 @@ def supported_AI_ranges_for_non_differential_input(device_name, AI_ranges):
227311

228312

229313
def supports_semiperiod_measurement(device_name):
314+
"""Empirically determines if the DAQ supports semiperiod measurement.
315+
316+
Args:
317+
device_name (str): NI-MAX device name.
318+
319+
Returns:
320+
bool: True if semi-period measurements are supported by the device.
321+
"""
230322
import warnings
231323

232324
with warnings.catch_warnings():
@@ -242,7 +334,9 @@ def supports_semiperiod_measurement(device_name):
242334

243335

244336
def get_min_semiperiod_measurement(device_name):
245-
"""Depending on the timebase used, counter inputs can measure time intervals of
337+
"""Determines the minimum semi-period measurement time supported by the device.
338+
339+
Depending on the timebase used, counter inputs can measure time intervals of
246340
various ranges. As a default, we pick a largish range - the one with the fastest
247341
timebase still capable of measuring 100 seconds, or the largest time interval if it
248342
is less than 100 seconds, and we save the smallest interval measurable with this
@@ -258,7 +352,14 @@ def get_min_semiperiod_measurement(device_name):
258352
possibility of timing out. For now (in the wait monitor worker class) we
259353
pessimistically add one second to the expected longest measurement to account for
260354
software delays. These decisions can be revisited if there is a need, do not
261-
hesitate to file an issue on bitbucket regarding this if it affects you."""
355+
hesitate to file an issue on bitbucket regarding this if it affects you.
356+
357+
Args:
358+
device_name (str): NI-MAX device name
359+
360+
Returns:
361+
float: Minimum measurement time.
362+
"""
262363
CI_chans = DAQmxGetDevCIPhysicalChans(device_name)
263364
CI_chan = device_name + '/' + CI_chans[0]
264365
# Make a task with a semiperiod measurement

0 commit comments

Comments
 (0)