Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
cc59b8d
start with a stub and some tests
mikeprosserni Sep 11, 2025
412ad38
Implement write_analog_waveform method in interpreters and update Ana…
mikeprosserni Sep 11, 2025
723b082
Add support for writing AnalogWaveform in Task class and implement re…
mikeprosserni Sep 11, 2025
cd5b34d
cleanup
mikeprosserni Sep 11, 2025
d9827a4
cleanup
mikeprosserni Sep 11, 2025
d97a9da
Refactor counter output data validation to check for Iterable type
mikeprosserni Sep 12, 2025
0aa0670
misc feedback
mikeprosserni Sep 12, 2025
83456c2
Refactor write_analog_waveform method to use write_analog_f64 and add…
mikeprosserni Sep 12, 2025
4a39288
test improvements
mikeprosserni Sep 12, 2025
4b74acb
use AnalogWaveform[Any] for all write() type annotations
mikeprosserni Sep 12, 2025
eb1dbb0
write_analog_waveforms and _internal_write_analog_waveform_per_chan
mikeprosserni Sep 12, 2025
d3d94c8
Merge remote-tracking branch 'origin/master' into users/mprosser/task…
mikeprosserni Sep 12, 2025
bd55ace
Add write_waveforms method to AnalogMultiChannelWriter and update tas…
mikeprosserni Sep 12, 2025
9381014
tests
mikeprosserni Sep 15, 2025
13aa1f6
Merge remote-tracking branch 'origin/master' into users/mprosser/task…
mikeprosserni Sep 15, 2025
65c41fa
style cleanup
mikeprosserni Sep 15, 2025
d155dce
fix styling
mikeprosserni Sep 15, 2025
6c56431
cleanup
mikeprosserni Sep 15, 2025
519423e
cleanup
mikeprosserni Sep 15, 2025
1f9f327
cleanup and write_waveforms()
mikeprosserni Sep 16, 2025
f0186b6
move logic to write_waveform()
mikeprosserni Sep 16, 2025
11be749
cleanup
mikeprosserni Sep 16, 2025
97851cc
Merge remote-tracking branch 'origin/master' into users/mprosser/task…
mikeprosserni Sep 16, 2025
071cc8c
fix test name
mikeprosserni Sep 16, 2025
6b80f72
number_of_samples_per_channel = waveforms[0].sample_count
mikeprosserni Sep 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions generated/nidaqmx/_library_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6975,17 +6975,13 @@ def write_analog_waveform(
timeout: float
) -> int:
"""Write an analog waveform."""
write_array = waveform.scaled_data
if not write_array.flags.c_contiguous:
write_array = write_array.copy(order="C")

return self.write_analog_f64(
task_handle,
waveform.sample_count,
auto_start,
timeout,
FillMode.GROUP_BY_CHANNEL.value,
write_array,
self._get_write_array(waveform),
)

def write_analog_waveforms(
Expand All @@ -6999,17 +6995,14 @@ def write_analog_waveforms(
assert len(waveforms) > 0
num_samps_per_chan = waveforms[0].sample_count

write_arrays = []
for waveform in waveforms:
if not waveform.sample_count == num_samps_per_chan:
if waveform.sample_count != num_samps_per_chan:
raise DaqError(
"The waveforms must all have the same sample count.",
DAQmxErrors.UNKNOWN
)
write_array = waveform.scaled_data
if not write_array.flags.c_contiguous:
write_array = write_array.copy(order="C")
write_arrays.append(write_array)

write_arrays = [self._get_write_array(waveform) for waveform in waveforms]

error_code, samples_written = self._internal_write_analog_waveform_per_chan(
task_handle,
Expand Down Expand Up @@ -7072,6 +7065,12 @@ def _internal_write_analog_waveform_per_chan(

return error_code, samps_per_chan_written.value

def _get_write_array(self, waveform: AnalogWaveform[Any]) -> numpy.typing.NDArray[numpy.float64]:
scaled_data = waveform.scaled_data
if scaled_data.flags.c_contiguous:
return scaled_data
return scaled_data.copy(order="C")


def write_raw(
self, task_handle, num_samps_per_chan, auto_start, timeout, numpy_array):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def write_one_sample(self, data, timeout=10):
def write_waveforms(
self, waveforms: Sequence[AnalogWaveform[Any]], timeout: float = 10.0
) -> int:
"""Writes one or more waveforms to one or more analog output channels in a task.
"""Writes waveforms to one or more analog output channels in a task.

If the task uses on-demand timing, this method returns only
after the device generates all samples. On-demand is the default
Expand Down
175 changes: 126 additions & 49 deletions generated/nidaqmx/task/_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import warnings
from collections.abc import Iterable
from enum import Enum
from typing import Any, NoReturn, Sequence

import numpy
from nitypes.waveform import AnalogWaveform, DigitalWaveform
Expand Down Expand Up @@ -1267,7 +1268,7 @@ def wait_until_done(self, timeout=10.0):
"""
self._interpreter.wait_until_task_done(self._handle, timeout)

def _raise_invalid_num_lines_error(self, num_lines_expected, num_lines_in_data):
def _raise_invalid_num_lines_error(self, num_lines_expected, num_lines_in_data) -> NoReturn:
raise DaqError(
"Specified read or write operation failed, because the number "
"of lines in the data for a channel does not match the number "
Expand All @@ -1281,7 +1282,9 @@ def _raise_invalid_num_lines_error(self, num_lines_expected, num_lines_in_data):
task_name=self.name,
)

def _raise_invalid_write_num_chans_error(self, number_of_channels, number_of_channels_in_data):
def _raise_invalid_write_num_chans_error(
self, number_of_channels, number_of_channels_in_data
) -> NoReturn:

raise DaqError(
"Write cannot be performed, because the number of channels in the "
Expand All @@ -1295,7 +1298,7 @@ def _raise_invalid_write_num_chans_error(self, number_of_channels, number_of_cha
task_name=self.name,
)

def _raise_invalid_write_mixed_data_error(self):
def _raise_invalid_write_mixed_data_error(self) -> NoReturn:
raise DaqError(
"Write cannot be performed, because the list contains a mix of "
"AnalogWaveform and non-AnalogWaveform objects. All elements in "
Expand All @@ -1304,6 +1307,22 @@ def _raise_invalid_write_mixed_data_error(self):
task_name=self.name,
)

def _raise_no_output_channels_error(self) -> NoReturn:
raise DaqError(
"Write failed, because there are no output channels in this "
"task to which data can be written.",
DAQmxErrors.WRITE_NO_OUTPUT_CHANS_IN_TASK,
task_name=self.name,
)

def _raise_unsupported_output_type_error(self, output_type) -> NoReturn:
raise DaqError(
"Write failed, because the output type is not supported.\n\n"
f"Output type: {output_type}",
DAQmxErrors.UNKNOWN,
task_name=self.name,
)

def write(self, data, auto_start=AUTO_START_UNSET, timeout=10.0):
"""Writes samples to the task or virtual channels you specify.

Expand Down Expand Up @@ -1372,6 +1391,11 @@ def write(self, data, auto_start=AUTO_START_UNSET, timeout=10.0):
Specifies the actual number of samples this method
successfully wrote.
"""
if isinstance(data, AnalogWaveform) or (
isinstance(data, list) and data and all(isinstance(wf, AnalogWaveform) for wf in data)
):
return self.write_waveform(data, auto_start, timeout)

channels_to_write = self.channels
number_of_channels = len(channels_to_write.channel_names)
write_chan_type = channels_to_write.chan_type
Expand All @@ -1392,13 +1416,6 @@ def write(self, data, auto_start=AUTO_START_UNSET, timeout=10.0):
number_of_samples_per_channel = len(data)
element = data[0]

elif isinstance(data, AnalogWaveform):
WAVEFORM_SUPPORT.raise_if_disabled()
if number_of_channels != 1:
self._raise_invalid_write_num_chans_error(number_of_channels, 1)
number_of_samples_per_channel = data.sample_count
element = data.scaled_data[0]

else:
number_of_samples_per_channel = 1
element = data
Expand All @@ -1408,18 +1425,9 @@ def write(self, data, auto_start=AUTO_START_UNSET, timeout=10.0):
if len(data) != number_of_channels:
self._raise_invalid_write_num_chans_error(number_of_channels, len(data))

if isinstance(data[0], AnalogWaveform):
WAVEFORM_SUPPORT.raise_if_disabled()
if not all(isinstance(wf, AnalogWaveform) for wf in data):
self._raise_invalid_write_mixed_data_error()

number_of_samples_per_channel = data[0].sample_count
element = data[0].scaled_data[0]

elif isinstance(data[0], list):
if isinstance(data[0], list):
number_of_samples_per_channel = len(data[0])
element = data[0][0]

else:
number_of_samples_per_channel = 1
element = data[0]
Expand All @@ -1445,24 +1453,15 @@ def write(self, data, auto_start=AUTO_START_UNSET, timeout=10.0):
auto_start = True

if write_chan_type == ChannelType.ANALOG_OUTPUT:
if isinstance(data, AnalogWaveform):
return self._interpreter.write_analog_waveform(
self._handle, data, auto_start, timeout
)
elif isinstance(data, list) and isinstance(data[0], AnalogWaveform):
return self._interpreter.write_analog_waveforms(
self._handle, data, auto_start, timeout
)
else:
data = numpy.asarray(data, dtype=numpy.float64)
return self._interpreter.write_analog_f64(
self._handle,
number_of_samples_per_channel,
auto_start,
timeout,
FillMode.GROUP_BY_CHANNEL.value,
data,
)
data = numpy.asarray(data, dtype=numpy.float64)
return self._interpreter.write_analog_f64(
self._handle,
number_of_samples_per_channel,
auto_start,
timeout,
FillMode.GROUP_BY_CHANNEL.value,
data,
)

elif write_chan_type == ChannelType.DIGITAL_OUTPUT:
if self.out_stream.do_num_booleans_per_chan == 1:
Expand Down Expand Up @@ -1573,20 +1572,98 @@ def write(self, data, auto_start=AUTO_START_UNSET, timeout=10.0):
)

else:
raise DaqError(
"Write failed, because the output type is not supported.\n\n"
f"Output type: {output_type}",
DAQmxErrors.UNKNOWN,
task_name=self.name,
self._raise_unsupported_output_type_error(output_type)

else:
self._raise_no_output_channels_error()

@requires_feature(WAVEFORM_SUPPORT)
def write_waveform(
self,
waveforms: AnalogWaveform[Any] | Sequence[AnalogWaveform[Any]],
auto_start=AUTO_START_UNSET,
timeout: float = 10.0,
) -> int:
"""Writes samples from one or more waveforms to the task or virtual channels you specify.

If the task uses on-demand timing, this method returns only
after the device generates all samples. On-demand is the default
timing type if you do not use the timing property on the task to
configure a sample timing type. If the task uses any timing type
other than on-demand, this method returns immediately and does
not wait for the device to generate all samples. Your
application must determine if the task is done to ensure that
the device generated all samples.

Args:
waveforms (AnalogWaveform[Any] or Sequence[AnalogWaveform[Any]]):
Contains the waveforms to write to the task.

The data you write must be in the units of the
generation, including any custom scales. Use the DAQmx
Create Channel methods to specify these units.
auto_start (Optional[bool]): Specifies if this method
automatically starts the task if you did not explicitly
start it with the DAQmx Start Task method.

The default value of this parameter depends on whether
you specify one sample or many samples to write to each
channel. If one sample per channel was specified, the
default value is True. If multiple samples per channel
were specified, the default value is False.
timeout (Optional[float]): Specifies the amount of time in
seconds to wait for the method to write all samples.
NI-DAQmx performs a timeout check only if the method
must wait before it writes data. This method returns an
error if the time elapses. The default timeout is 10
seconds. If you set timeout to
nidaqmx.constants.WAIT_INFINITELY, the method waits
indefinitely. If you set timeout to 0, the method tries
once to write the submitted samples. If the method could
not write all the submitted samples, it returns an error
and the number of samples successfully written.

Returns:
int:

Specifies the actual number of samples this method
successfully wrote.
"""
channels_to_write = self.channels
number_of_channels = len(channels_to_write.channel_names)
write_chan_type = channels_to_write.chan_type

if isinstance(waveforms, AnalogWaveform):
number_of_samples_per_channel = waveforms.sample_count
elif isinstance(waveforms, list):
number_of_samples_per_channel = len(waveforms)
else:
number_of_samples_per_channel = 1

if auto_start is AUTO_START_UNSET:
if number_of_samples_per_channel > 1:
auto_start = False
else:
auto_start = True

if write_chan_type == ChannelType.ANALOG_OUTPUT:
if isinstance(waveforms, AnalogWaveform):
if number_of_channels != 1:
self._raise_invalid_write_num_chans_error(number_of_channels, 1)
return self._interpreter.write_analog_waveform(
self._handle, waveforms, auto_start, timeout
)
elif isinstance(waveforms, list) and isinstance(waveforms[0], AnalogWaveform):
if number_of_channels != len(waveforms):
self._raise_invalid_write_num_chans_error(number_of_channels, len(waveforms))
return self._interpreter.write_analog_waveforms(
self._handle, waveforms, auto_start, timeout
)
else:
self._raise_unsupported_output_type_error(type(waveforms))

else:
raise DaqError(
"Write failed, because there are no output channels in this "
"task to which data can be written.",
DAQmxErrors.WRITE_NO_OUTPUT_CHANS_IN_TASK,
task_name=self.name,
)
self._raise_no_output_channels_error()


class _TaskAlternateConstructor(Task):
Expand Down
21 changes: 10 additions & 11 deletions src/codegen/templates/_library_interpreter.py.mako
Original file line number Diff line number Diff line change
Expand Up @@ -802,17 +802,13 @@ class LibraryInterpreter(BaseInterpreter):
timeout: float
) -> int:
"""Write an analog waveform."""
write_array = waveform.scaled_data
if not write_array.flags.c_contiguous:
write_array = write_array.copy(order="C")

return self.write_analog_f64(
task_handle,
waveform.sample_count,
auto_start,
timeout,
FillMode.GROUP_BY_CHANNEL.value,
write_array,
self._get_write_array(waveform),
)

## write_analog_waveforms has special handling
Expand All @@ -827,17 +823,14 @@ class LibraryInterpreter(BaseInterpreter):
assert len(waveforms) > 0
num_samps_per_chan = waveforms[0].sample_count

write_arrays = []
for waveform in waveforms:
if not waveform.sample_count == num_samps_per_chan:
if waveform.sample_count != num_samps_per_chan:
raise DaqError(
"The waveforms must all have the same sample count.",
DAQmxErrors.UNKNOWN
)
write_array = waveform.scaled_data
if not write_array.flags.c_contiguous:
write_array = write_array.copy(order="C")
write_arrays.append(write_array)

write_arrays = [self._get_write_array(waveform) for waveform in waveforms]

error_code, samples_written = self._internal_write_analog_waveform_per_chan(
task_handle,
Expand Down Expand Up @@ -900,6 +893,12 @@ class LibraryInterpreter(BaseInterpreter):

return error_code, samps_per_chan_written.value

def _get_write_array(self, waveform: AnalogWaveform[Any]) -> numpy.typing.NDArray[numpy.float64]:
scaled_data = waveform.scaled_data
if scaled_data.flags.c_contiguous:
return scaled_data
return scaled_data.copy(order="C")


## The datatype of 'write_array' is incorrect in daqmxAPISharp.json file.
def write_raw(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def write_one_sample(self, data, timeout=10):
def write_waveforms(
self, waveforms: Sequence[AnalogWaveform[Any]], timeout: float = 10.0
) -> int:
"""Writes one or more waveforms to one or more analog output channels in a task.
"""Writes waveforms to one or more analog output channels in a task.

If the task uses on-demand timing, this method returns only
after the device generates all samples. On-demand is the default
Expand Down
Loading
Loading