Skip to content

Commit 1f92791

Browse files
Add AnalogMultiChannelWriter.write_waveforms (#834)
* start with a stub and some tests * Implement write_analog_waveform method in interpreters and update AnalogSingleChannelWriter to use it * Add support for writing AnalogWaveform in Task class and implement related tests * cleanup * cleanup * Refactor counter output data validation to check for Iterable type * misc feedback * Refactor write_analog_waveform method to use write_analog_f64 and add tests for scaling and noncontiguous arrays * test improvements * use AnalogWaveform[Any] for all write() type annotations * write_analog_waveforms and _internal_write_analog_waveform_per_chan * Add write_waveforms method to AnalogMultiChannelWriter and update task handling for AnalogWaveform lists * tests * style cleanup * fix styling * cleanup * cleanup * cleanup and write_waveforms() * move logic to write_waveform() * cleanup * fix test name * number_of_samples_per_channel = waveforms[0].sample_count
1 parent bb455d1 commit 1f92791

File tree

15 files changed

+1153
-145
lines changed

15 files changed

+1153
-145
lines changed

generated/nidaqmx/_base_interpreter.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1919,3 +1919,13 @@ def write_analog_waveform(
19191919
timeout: float
19201920
) -> int:
19211921
raise NotImplementedError
1922+
1923+
@abc.abstractmethod
1924+
def write_analog_waveforms(
1925+
self,
1926+
task_handle: object,
1927+
waveforms: Sequence[AnalogWaveform[Any]],
1928+
auto_start: bool,
1929+
timeout: float
1930+
) -> int:
1931+
raise NotImplementedError

generated/nidaqmx/_grpc_interpreter.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3664,6 +3664,15 @@ def write_analog_waveform(
36643664
) -> int:
36653665
raise NotImplementedError
36663666

3667+
def write_analog_waveforms(
3668+
self,
3669+
task_handle: object,
3670+
waveforms: Sequence[AnalogWaveform[typing.Any]],
3671+
auto_start: bool,
3672+
timeout: float
3673+
) -> int:
3674+
raise NotImplementedError
3675+
36673676
def _assign_numpy_array(numpy_array, grpc_array):
36683677
"""
36693678
Assigns grpc array to numpy array maintaining the original shape.

generated/nidaqmx/_library_interpreter.py

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6975,19 +6975,102 @@ def write_analog_waveform(
69756975
timeout: float
69766976
) -> int:
69776977
"""Write an analog waveform."""
6978-
array = waveform.scaled_data
6979-
if not array.flags.c_contiguous:
6980-
array = array.copy(order="C")
6981-
69826978
return self.write_analog_f64(
69836979
task_handle,
69846980
waveform.sample_count,
69856981
auto_start,
69866982
timeout,
69876983
FillMode.GROUP_BY_CHANNEL.value,
6988-
array,
6984+
self._get_write_array(waveform),
69896985
)
69906986

6987+
def write_analog_waveforms(
6988+
self,
6989+
task_handle: object,
6990+
waveforms: Sequence[AnalogWaveform[Any]],
6991+
auto_start: bool,
6992+
timeout: float
6993+
) -> int:
6994+
"""Write analog waveforms."""
6995+
assert len(waveforms) > 0
6996+
num_samps_per_chan = waveforms[0].sample_count
6997+
6998+
for waveform in waveforms:
6999+
if waveform.sample_count != num_samps_per_chan:
7000+
raise DaqError(
7001+
"The waveforms must all have the same sample count.",
7002+
DAQmxErrors.UNKNOWN
7003+
)
7004+
7005+
write_arrays = [self._get_write_array(waveform) for waveform in waveforms]
7006+
7007+
error_code, samples_written = self._internal_write_analog_waveform_per_chan(
7008+
task_handle,
7009+
num_samps_per_chan,
7010+
auto_start,
7011+
timeout,
7012+
write_arrays,
7013+
)
7014+
7015+
self.check_for_error(error_code, samps_per_chan_written=samples_written)
7016+
return samples_written
7017+
7018+
def _internal_write_analog_waveform_per_chan(
7019+
self,
7020+
task_handle: object,
7021+
num_samps_per_chan: int,
7022+
auto_start: bool,
7023+
timeout: float,
7024+
write_arrays: Sequence[numpy.typing.NDArray[numpy.float64]],
7025+
) -> Tuple[
7026+
int, # error code
7027+
int, # The number of samples per channel that were written
7028+
]:
7029+
assert isinstance(task_handle, TaskHandle)
7030+
samps_per_chan_written = ctypes.c_int()
7031+
7032+
channel_count = len(write_arrays)
7033+
assert channel_count > 0
7034+
assert all(write_array.size >= num_samps_per_chan for write_array in write_arrays)
7035+
7036+
cfunc = lib_importer.windll.DAQmxInternalWriteAnalogWaveformPerChan
7037+
if cfunc.argtypes is None:
7038+
with cfunc.arglock:
7039+
if cfunc.argtypes is None:
7040+
cfunc.argtypes = [
7041+
TaskHandle,
7042+
ctypes.c_int,
7043+
c_bool32,
7044+
ctypes.c_double,
7045+
ctypes.POINTER(ctypes.POINTER(ctypes.c_double)),
7046+
ctypes.c_uint,
7047+
ctypes.POINTER(ctypes.c_int),
7048+
ctypes.POINTER(c_bool32),
7049+
]
7050+
7051+
write_array_ptrs = (ctypes.POINTER(ctypes.c_double) * channel_count)()
7052+
for i, write_array in enumerate(write_arrays):
7053+
write_array_ptrs[i] = write_array.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
7054+
7055+
error_code = cfunc(
7056+
task_handle,
7057+
num_samps_per_chan,
7058+
auto_start,
7059+
timeout,
7060+
write_array_ptrs,
7061+
channel_count,
7062+
ctypes.byref(samps_per_chan_written),
7063+
None,
7064+
)
7065+
7066+
return error_code, samps_per_chan_written.value
7067+
7068+
def _get_write_array(self, waveform: AnalogWaveform[Any]) -> numpy.typing.NDArray[numpy.float64]:
7069+
scaled_data = waveform.scaled_data
7070+
if scaled_data.flags.c_contiguous:
7071+
return scaled_data
7072+
return scaled_data.copy(order="C")
7073+
69917074

69927075
def write_raw(
69937076
self, task_handle, num_samps_per_chan, auto_start, timeout, numpy_array):

generated/nidaqmx/stream_writers/_analog_multi_channel_writer.py

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Sequence
4+
from typing import Any
5+
6+
from nitypes.waveform import AnalogWaveform
7+
8+
from nidaqmx._feature_toggles import WAVEFORM_SUPPORT, requires_feature
19
from nidaqmx.constants import FillMode
210
from nidaqmx.stream_writers._channel_writer_base import (
311
AUTO_START_UNSET,
@@ -41,9 +49,7 @@ def write_many_sample(self, data, timeout=10.0):
4149
and the number of samples successfully written.
4250
4351
Returns:
44-
int:
45-
46-
Specifies the actual number of samples this method
52+
int: Specifies the actual number of samples this method
4753
successfully wrote to each channel in the task.
4854
""" # noqa: W505 - doc line too long (101 > 100 characters) (auto-generated noqa)
4955
self._verify_array(data, True, True)
@@ -84,3 +90,48 @@ def write_one_sample(self, data, timeout=10):
8490
return self._interpreter.write_analog_f64(
8591
self._handle, 1, auto_start, timeout, FillMode.GROUP_BY_CHANNEL.value, data
8692
)
93+
94+
@requires_feature(WAVEFORM_SUPPORT)
95+
def write_waveforms(
96+
self, waveforms: Sequence[AnalogWaveform[Any]], timeout: float = 10.0
97+
) -> int:
98+
"""Writes waveforms to one or more analog output channels in a task.
99+
100+
If the task uses on-demand timing, this method returns only
101+
after the device generates all samples. On-demand is the default
102+
timing type if you do not use the timing property on the task to
103+
configure a sample timing type. If the task uses any timing type
104+
other than on-demand, this method returns immediately and does
105+
not wait for the device to generate all samples. Your
106+
application must determine if the task is done to ensure that
107+
the device generated all samples.
108+
109+
Args:
110+
waveforms (Sequence[AnalogWaveform[Any]]): Contains one or
111+
more waveforms to write to the task. All waveforms must
112+
have the same number of samples.
113+
114+
Each waveform corresponds to a channel in the task.
115+
The order of the waveforms corresponds to
116+
the order in which you add the channels to the task.
117+
timeout (Optional[float]): Specifies the amount of time in
118+
seconds to wait for the method to write all samples.
119+
NI-DAQmx performs a timeout check only if the method
120+
must wait before it writes data. This method returns an
121+
error if the time elapses. The default timeout is 10
122+
seconds. If you set timeout to
123+
nidaqmx.constants.WAIT_INFINITELY, the method waits
124+
indefinitely. If you set timeout to 0, the method tries
125+
once to write the submitted samples. If the method could
126+
not write all the submitted samples, it returns an error
127+
and the number of samples successfully written.
128+
129+
Returns:
130+
int: Specifies the actual number of samples this method
131+
successfully wrote to each channel in the task.
132+
"""
133+
auto_start = self._auto_start if self._auto_start is not AUTO_START_UNSET else False
134+
135+
return self._interpreter.write_analog_waveforms(
136+
self._handle, waveforms, auto_start, timeout
137+
)

0 commit comments

Comments
 (0)