Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
695ca70
remove all "not implemented in GRPC" test markers
Nov 3, 2025
f48fadd
implement methods in _grpc_interpreter.mako
Nov 3, 2025
01c7e01
update changelog
Nov 3, 2025
f79fe98
update nidaqmx.proto and stub_generator.py
Nov 3, 2025
43b535a
fix lint and some mypy issues
Nov 3, 2025
366a181
generate .pyi for timestamp and waveform
Nov 3, 2025
fc7ca75
_fix_ni_protobuf_imports()
Nov 3, 2025
6b6e32d
lint
Nov 3, 2025
4a98ac6
Sort proto files for deterministic generation order
Nov 3, 2025
d3e08b4
lint
Nov 3, 2025
6ac4caa
normalize line endings
Nov 3, 2025
92e5983
revert sorting fix
Nov 3, 2025
6e74004
lint
Nov 3, 2025
1c66bf9
fix some line endings
Nov 4, 2025
8c39249
fix stub_generator line endings
Nov 4, 2025
44784cf
write_bytes in stub_generator
Nov 4, 2025
d80142b
stub_generator cleanup
Nov 4, 2025
6741719
force crlf endings
Nov 4, 2025
6fb3c2a
line endings
Nov 4, 2025
da90124
cleanup
Nov 4, 2025
53fa057
cleanup
Nov 4, 2025
bafb528
fix line endings
Nov 4, 2025
c72d99e
cleanup
Nov 4, 2025
45d00bf
cleanup
Nov 4, 2025
758e88d
_get_num_samps_per_chan helper
Nov 4, 2025
1ab1022
cleanup
Nov 4, 2025
66d6482
improve changelog
Nov 10, 2025
8fbd911
Merge remote-tracking branch 'origin/master' into users/mprosser/task…
Nov 11, 2025
59afa14
_waveform_utils.py
Nov 11, 2025
50725fc
add ni-protobuf-types dependency and revert changes in stub_generator
Nov 11, 2025
bad6ef4
grpc interpreter cleanup
Nov 11, 2025
535b2be
use waveform_conversion methods
Nov 11, 2025
a48e3aa
cleanup
Nov 11, 2025
8900225
target_waveform.timing = temp_waveform.timing
Nov 11, 2025
0b50c8e
simplify read_analog_waveforms and read_digital_waveforms
Nov 11, 2025
f9b1d61
simplify write_analog_waveforms and write_digital_waveforms
Nov 11, 2025
3630130
cleanup
Nov 11, 2025
da81015
revert _handle_rpc_error
Nov 11, 2025
f2cbbe0
cleanup
Nov 11, 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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ All notable changes to this project will be documented in this file.

* ### Major Changes
* Removed support for Python 3.9.
* (IN PROGRESS behind "WAVEFORM_SUPPORT" feature toggle) Added support for reading and writing Waveform data through gRPC using [NI gRPC Device Server](https://github.com/ni/grpc-device).

* ### Known Issues
* ...
Expand All @@ -47,7 +48,7 @@ All notable changes to this project will be documented in this file.
* [639: first_samp_timestamp_val property does not work because LibraryInterpreter is missing a method](https://github.com/ni/nidaqmx-python/issues/639)

* ### Major Changes
* (IN PROGRESS behind "WAVEFORM_SUPPORT" feature toggle) Added support for reading and writing Waveform data.
* (IN PROGRESS behind "WAVEFORM_SUPPORT" feature toggle) Added support for reading and writing Waveform data.
* Simplify `numpy` and `click` version constraints.
* `nidaqmx` supports a wider range of Python versions than `numpy` and `click`, so testing
`nidaqmx` against its oldest supported Python version (currently 3.9) requires downgrading
Expand Down
6 changes: 3 additions & 3 deletions generated/nidaqmx/_base_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1879,7 +1879,7 @@ def read_analog_waveforms(
waveform_attribute_mode: WaveformAttributeMode
) -> int:
raise NotImplementedError

@abc.abstractmethod
def read_digital_waveform(
self,
Expand Down Expand Up @@ -1935,7 +1935,7 @@ def write_analog_waveforms(
timeout: float
) -> int:
raise NotImplementedError

@abc.abstractmethod
def write_digital_waveform(
self,
Expand All @@ -1945,7 +1945,7 @@ def write_digital_waveform(
timeout: float,
) -> int:
raise NotImplementedError

def write_digital_waveforms(
self,
task_handle: object,
Expand Down
133 changes: 123 additions & 10 deletions generated/nidaqmx/_grpc_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,17 @@
from nidaqmx._base_interpreter import BaseEventHandler, BaseInterpreter
from nidaqmx._stubs import nidaqmx_pb2 as grpc_types
from nidaqmx._stubs import nidaqmx_pb2_grpc as nidaqmx_grpc
from ni.protobuf.types.waveform_conversion import (
digital_waveform_from_protobuf,
digital_waveform_to_protobuf,
float64_analog_waveform_from_protobuf,
float64_analog_waveform_to_protobuf
)
from nidaqmx.constants import WaveformAttributeMode
from nidaqmx.error_codes import DAQmxErrors
from nidaqmx._grpc_time import convert_time_to_timestamp, convert_timestamp_to_time
from nidaqmx._waveform_utils import get_num_samps_per_chan
from session_pb2 import Session

_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -3616,7 +3624,13 @@ def read_analog_waveform(
waveform: AnalogWaveform[numpy.float64],
waveform_attribute_mode: WaveformAttributeMode
) -> int:
raise NotImplementedError
return self.read_analog_waveforms(
task_handle,
number_of_samples_per_channel,
timeout,
[waveform],
waveform_attribute_mode
)

def read_analog_waveforms(
self,
Expand All @@ -3626,7 +3640,30 @@ def read_analog_waveforms(
waveforms: Sequence[AnalogWaveform[numpy.float64]],
waveform_attribute_mode: WaveformAttributeMode
) -> int:
raise NotImplementedError
assert isinstance(task_handle, Session)
response = self._invoke(
self._client.ReadAnalogWaveforms,
grpc_types.ReadAnalogWaveformsRequest(
task=task_handle,
num_samps_per_chan=number_of_samples_per_channel,
timeout=timeout,
waveform_attribute_mode_raw=waveform_attribute_mode.value
))

if len(response.waveforms) != len(waveforms):
raise ValueError(f"Expected {len(waveforms)} waveforms but received {len(response.waveforms)} from server")

for i, grpc_waveform in enumerate(response.waveforms):
temp_waveform = float64_analog_waveform_from_protobuf(grpc_waveform)
waveforms[i].load_data(temp_waveform.scaled_data)

waveforms[i].scale_mode = temp_waveform.scale_mode
waveforms[i].timing = temp_waveform.timing
waveforms[i].extended_properties.clear()
waveforms[i].extended_properties.update(temp_waveform.extended_properties)

self._check_for_error_from_response(response.status, samps_per_chan_read=response.samps_per_chan_read)
return response.samps_per_chan_read

def read_digital_waveform(
self,
Expand All @@ -3636,7 +3673,15 @@ def read_digital_waveform(
waveform: DigitalWaveform[Any],
waveform_attribute_mode: WaveformAttributeMode
) -> int:
raise NotImplementedError
return self.read_digital_waveforms(
task_handle,
1, # channel_count
number_of_samples_per_channel,
waveform.signal_count, # number_of_signals_per_sample
timeout,
[waveform],
waveform_attribute_mode
)

def read_digital_waveforms(
self,
Expand All @@ -3648,7 +3693,32 @@ def read_digital_waveforms(
waveforms: Sequence[DigitalWaveform[Any]],
waveform_attribute_mode: WaveformAttributeMode,
) -> int:
raise NotImplementedError
assert isinstance(task_handle, Session)
response = self._invoke(
self._client.ReadDigitalWaveforms,
grpc_types.ReadDigitalWaveformsRequest(
task=task_handle,
num_samps_per_chan=number_of_samples_per_channel,
timeout=timeout,
waveform_attribute_mode_raw=waveform_attribute_mode.value
))

if len(response.waveforms) != len(waveforms):
raise ValueError(f"Expected {len(waveforms)} waveforms but received {len(response.waveforms)} from server")

for i, grpc_waveform in enumerate(response.waveforms):
temp_waveform = digital_waveform_from_protobuf(grpc_waveform)
data = temp_waveform.data
if data.dtype != waveforms[i].dtype:
data = data.view(waveforms[i].dtype)
waveforms[i].load_data(data)

waveforms[i].timing = temp_waveform.timing
waveforms[i].extended_properties.clear()
waveforms[i].extended_properties.update(temp_waveform.extended_properties)

self._check_for_error_from_response(response.status, samps_per_chan_read=response.samps_per_chan_read)
return response.samps_per_chan_read

def read_new_digital_waveforms(
self,
Expand All @@ -3659,7 +3729,20 @@ def read_new_digital_waveforms(
timeout: float,
waveform_attribute_mode: WaveformAttributeMode,
) -> Sequence[DigitalWaveform[numpy.uint8]]:
raise NotImplementedError
assert isinstance(task_handle, Session)
response = self._invoke(
self._client.ReadDigitalWaveforms,
grpc_types.ReadDigitalWaveformsRequest(
task=task_handle,
num_samps_per_chan=number_of_samples_per_channel,
timeout=timeout,
waveform_attribute_mode_raw=waveform_attribute_mode.value
))

waveforms = [digital_waveform_from_protobuf(grpc_waveform) for grpc_waveform in response.waveforms]

self._check_for_error_from_response(response.status, samps_per_chan_read=response.samps_per_chan_read)
return waveforms

def write_analog_waveform(
self,
Expand All @@ -3668,7 +3751,7 @@ def write_analog_waveform(
auto_start: bool,
timeout: float
) -> int:
raise NotImplementedError
return self.write_analog_waveforms(task_handle, [waveform], auto_start, timeout)

def write_analog_waveforms(
self,
Expand All @@ -3677,7 +3760,22 @@ def write_analog_waveforms(
auto_start: bool,
timeout: float
) -> int:
raise NotImplementedError
assert isinstance(task_handle, Session)
num_samps_per_chan = get_num_samps_per_chan(waveforms)

grpc_waveforms = [float64_analog_waveform_to_protobuf(waveform) for waveform in waveforms]

response = self._invoke(
self._client.WriteAnalogWaveforms,
grpc_types.WriteAnalogWaveformsRequest(
task=task_handle,
auto_start=auto_start,
timeout=timeout,
waveforms=grpc_waveforms
))

self._check_for_error_from_response(response.status, samps_per_chan_written=response.samps_per_chan_written)
return response.samps_per_chan_written

def write_digital_waveform(
self,
Expand All @@ -3686,16 +3784,31 @@ def write_digital_waveform(
auto_start: bool,
timeout: float,
) -> int:
raise NotImplementedError
return self.write_digital_waveforms(task_handle, [waveform], auto_start, timeout)

def write_digital_waveforms(
self,
task_handle: object,
waveform: Sequence[DigitalWaveform[Any]],
waveforms: Sequence[DigitalWaveform[Any]],
auto_start: bool,
timeout: float,
) -> int:
raise NotImplementedError
assert isinstance(task_handle, Session)
num_samps_per_chan = get_num_samps_per_chan(waveforms)

grpc_waveforms = [digital_waveform_to_protobuf(waveform) for waveform in waveforms]

response = self._invoke(
self._client.WriteDigitalWaveforms,
grpc_types.WriteDigitalWaveformsRequest(
task=task_handle,
auto_start=auto_start,
timeout=timeout,
waveforms=grpc_waveforms
))

self._check_for_error_from_response(response.status, samps_per_chan_written=response.samps_per_chan_written)
return response.samps_per_chan_written

def _assign_numpy_array(numpy_array, grpc_array):
"""
Expand Down
21 changes: 3 additions & 18 deletions generated/nidaqmx/_library_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from nidaqmx.error_codes import DAQmxErrors, DAQmxWarnings
from nidaqmx.errors import DaqError, DaqFunctionNotSupportedError, DaqReadError, DaqWarning, DaqWriteError
from nidaqmx._lib_time import AbsoluteTime
from nidaqmx._waveform_utils import get_num_samps_per_chan
from nitypes.waveform.typing import ExtendedPropertyValue
from nitypes.waveform import AnalogWaveform, DigitalWaveform, SampleIntervalMode, Timing, ExtendedPropertyDictionary

Expand Down Expand Up @@ -7011,15 +7012,7 @@ def write_analog_waveforms(
timeout: float
) -> int:
"""Write analog waveforms."""
assert len(waveforms) > 0
num_samps_per_chan = waveforms[0].sample_count

for waveform in waveforms:
if waveform.sample_count != num_samps_per_chan:
raise DaqError(
"The waveforms must all have the same sample count.",
DAQmxErrors.UNKNOWN
)
num_samps_per_chan = get_num_samps_per_chan(waveforms)

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

Expand Down Expand Up @@ -7130,15 +7123,7 @@ def write_digital_waveforms(
) -> int:
"""Write digital waveforms."""
channel_count = len(waveforms)
assert channel_count > 0
sample_count = waveforms[0].sample_count

for waveform in waveforms:
if waveform.sample_count != sample_count:
raise DaqError(
"The waveforms must all have the same sample count.",
DAQmxErrors.UNKNOWN
)
sample_count = get_num_samps_per_chan(waveforms)

bytes_per_chan_array = numpy.array([wf.signal_count for wf in waveforms], dtype=numpy.uint32)

Expand Down
4,625 changes: 2,322 additions & 2,303 deletions generated/nidaqmx/_stubs/nidaqmx_pb2.py

Large diffs are not rendered by default.

Loading