From a90c4e7d2e20facfb445f5afe8b3153e17aa0171 Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Tue, 9 Sep 2025 15:49:29 -0500 Subject: [PATCH 1/9] Split out stream writer tests into separate files --- tests/component/conftest.py | 387 +++++++++- .../test_digital_multi_channel_reader.py | 30 +- .../test_digital_single_channel_reader.py | 72 +- .../test_analog_multi_channel_writer.py | 80 ++ .../test_analog_single_channel_writer.py | 55 ++ .../test_analog_unscaled_writer.py | 182 +++++ .../test_digital_multi_channel_writer.py | 265 +++++++ .../test_digital_single_channel_writer.py | 184 +++++ tests/component/test_stream_writers_ao.py | 400 ---------- tests/component/test_stream_writers_do.py | 697 ------------------ 10 files changed, 1198 insertions(+), 1154 deletions(-) create mode 100644 tests/component/stream_writers/test_analog_multi_channel_writer.py create mode 100644 tests/component/stream_writers/test_analog_single_channel_writer.py create mode 100644 tests/component/stream_writers/test_analog_unscaled_writer.py create mode 100644 tests/component/stream_writers/test_digital_multi_channel_writer.py create mode 100644 tests/component/stream_writers/test_digital_single_channel_writer.py delete mode 100644 tests/component/test_stream_writers_ao.py delete mode 100644 tests/component/test_stream_writers_do.py diff --git a/tests/component/conftest.py b/tests/component/conftest.py index 7c53572e3..6976709a0 100644 --- a/tests/component/conftest.py +++ b/tests/component/conftest.py @@ -16,6 +16,27 @@ from nidaqmx.utils import flatten_channel_string +def _start_do_task(task: nidaqmx.Task, is_port: bool = False, num_chans: int = 1) -> None: + # We'll be doing on-demand, so start the task and drive all lines low + task.start() + if is_port: + if num_chans == 8: + task.write(0) + else: + task.write([0] * num_chans) + else: + if num_chans == 1: + task.write(False) + else: + task.write([False] * num_chans) + + +def _start_di_task(task: nidaqmx.Task) -> None: + # Don't reserve the lines, so we can read what DO is writing. + task.di_channels.all.di_tristate = False + task.start() + + # Simulated DAQ voltage data is a noisy sinewave within the range of the minimum and maximum values # of the virtual channel. We can leverage this behavior to validate we get the correct data from # the Python bindings. @@ -31,6 +52,10 @@ def _get_current_setpoint_for_chan(chan_index: int) -> float: return float(chan_index + 1) +def _get_expected_voltage_for_chan(chan_index: int) -> float: + return float(chan_index + 1) + + def _volts_to_codes(volts: float, max_code: int = 32767, max_voltage: float = 10.0) -> int: return int(volts * max_code / max_voltage) @@ -57,11 +82,15 @@ def _get_voltage_code_offset_for_chan(chan_index: int) -> int: return _volts_to_codes(voltage_limits) -def _get_num_lines_in_task(task: nidaqmx.Task) -> int: +def _get_num_di_lines_in_task(task: nidaqmx.Task) -> int: return sum([chan.di_num_lines for chan in task.channels]) -def _get_expected_digital_data_for_sample(num_lines: int, sample_number: int) -> int: +def _get_num_do_lines_in_task(task: nidaqmx.Task) -> int: + return sum([chan.do_num_lines for chan in task.channels]) + + +def _get_digital_data_for_sample(num_lines: int, sample_number: int) -> int: result = 0 # Simulated digital signals "count" from 0 in binary within each group of 8 lines. for _ in range((num_lines + 7) // 8): @@ -85,9 +114,9 @@ def _get_expected_data_for_line(num_samples: int, line_number: int) -> list[int] return data -def _get_expected_digital_data(num_lines: int, num_samples: int) -> list[int]: +def _get_digital_data(num_lines: int, num_samples: int) -> list[int]: return [ - _get_expected_digital_data_for_sample(num_lines, sample_number) + _get_digital_data_for_sample(num_lines, sample_number) for sample_number in range(num_samples) ] @@ -95,7 +124,7 @@ def _get_expected_digital_data(num_lines: int, num_samples: int) -> list[int]: def _get_expected_digital_port_data_port_major( task: nidaqmx.Task, num_samples: int ) -> list[list[int]]: - return [_get_expected_digital_data(chan.di_num_lines, num_samples) for chan in task.channels] + return [_get_digital_data(chan.di_num_lines, num_samples) for chan in task.channels] def _get_expected_digital_port_data_sample_major( @@ -105,6 +134,21 @@ def _get_expected_digital_port_data_sample_major( return numpy.transpose(result).tolist() +def _get_digital_port_data_for_sample(task: nidaqmx.Task, sample_number: int) -> list[int]: + return [ + _get_digital_data_for_sample(chan.do_num_lines, sample_number) for chan in task.channels + ] + + +def _get_digital_port_data_port_major(task: nidaqmx.Task, num_samples: int) -> list[list[int]]: + return [_get_digital_data(chan.do_num_lines, num_samples) for chan in task.channels] + + +def _get_digital_port_data_sample_major(task: nidaqmx.Task, num_samples: int) -> list[list[int]]: + result = _get_digital_port_data_port_major(task, num_samples) + return numpy.transpose(result).tolist() + + def _bool_array_to_int(bool_array: numpy.typing.NDArray[numpy.bool_]) -> int: result = 0 # Simulated data is little-endian @@ -113,6 +157,13 @@ def _bool_array_to_int(bool_array: numpy.typing.NDArray[numpy.bool_]) -> int: return result +def _int_to_bool_array(num_lines: int, input: int) -> numpy.typing.NDArray[numpy.bool_]: + result = numpy.full(num_lines, True, dtype=numpy.bool_) + for bit in range(num_lines): + result[bit] = (input & (1 << bit)) != 0 + return result + + def _get_waveform_data(waveform: DigitalWaveform) -> list[int]: assert isinstance(waveform, DigitalWaveform) return [_bool_array_to_int(sample) for sample in waveform.data] @@ -139,11 +190,17 @@ def _assert_equal_2d(data: list[list[float]], expected: list[list[float]], abs: _D = TypeVar("_D", bound=numpy.generic) -VOLTAGE_EPSILON = 1e-3 +VOLTAGE_EPSILON = 1e-2 VOLTAGE_CODE_EPSILON = round(_volts_to_codes(VOLTAGE_EPSILON)) POWER_EPSILON = 1e-3 POWER_BINARY_EPSILON = 1 +# NOTE: You can't scale from volts to codes correctly without knowing the internal calibration +# constants. The internal reference has a healthy amount of overrange to ensure we can calibrate to +# device specifications. I've used 10.1 volts above to approximate that, but 100mv of accuracy is +# also fine since the expected output of each channel value will be 1 volt apart. +VOLTAGE_EPSILON_FOR_RAW = 1e-1 + @pytest.fixture def ai_single_channel_task( @@ -525,3 +582,321 @@ def di_multi_channel_timing_task( ) task.timing.cfg_samp_clk_timing(1000.0, sample_mode=AcquisitionType.FINITE, samps_per_chan=50) return task + + +@pytest.fixture +def ao_single_channel_task( + generate_task: Callable[[], nidaqmx.Task], + real_x_series_multiplexed_device: nidaqmx.system.Device, +) -> nidaqmx.Task: + """Configure a single-channel AO task.""" + task = generate_task() + chan_index = 0 + offset = _get_expected_voltage_for_chan(chan_index) + chan = task.ao_channels.add_ao_voltage_chan( + real_x_series_multiplexed_device.ao_physical_chans[chan_index].name, + min_val=0.0, + max_val=offset + VOLTAGE_EPSILON, + ) + # forcing the maximum range for binary read scaling to be predictable + chan.ao_dac_rng_high = 10 + chan.ao_dac_rng_low = -10 + + # we'll be doing simple on-demand, so start the task now + task.start() + + # set the output to a known initial value + task.write(0.0) + + return task + + +@pytest.fixture +def ai_single_channel_loopback_task( + generate_task: Callable[[], nidaqmx.Task], + real_x_series_multiplexed_device: nidaqmx.system.Device, +) -> nidaqmx.Task: + """Configure a single-channel AI loopback task.""" + task = generate_task() + chan_index = 0 + task.ai_channels.add_ai_voltage_chan( + f"{real_x_series_multiplexed_device.name}/_ao{chan_index}_vs_aognd", + min_val=-10, + max_val=10, + ) + + # we'll be doing simple on-demand, so start the task now + task.start() + + return task + + +@pytest.fixture +def ao_multi_channel_task( + generate_task: Callable[[], nidaqmx.Task], + real_x_series_multiplexed_device: nidaqmx.system.Device, +) -> nidaqmx.Task: + """Configure a multi-channel AO task.""" + task = generate_task() + num_chans = 2 + for chan_index in range(num_chans): + offset = _get_expected_voltage_for_chan(chan_index) + chan = task.ao_channels.add_ao_voltage_chan( + real_x_series_multiplexed_device.ao_physical_chans[chan_index].name, + min_val=0.0, + max_val=offset + VOLTAGE_EPSILON, + ) + # forcing the maximum range for binary read scaling to be predictable + chan.ao_dac_rng_high = 10 + chan.ao_dac_rng_low = -10 + + # we'll be doing simple on-demand, so start the task now + task.start() + + # set the output to a known initial value + task.write([0.0] * num_chans) + + return task + + +@pytest.fixture +def ai_multi_channel_loopback_task( + generate_task: Callable[[], nidaqmx.Task], + real_x_series_multiplexed_device: nidaqmx.system.Device, +) -> nidaqmx.Task: + """Configure a multi-channel AI loopback task.""" + task = generate_task() + num_chans = 2 + for chan_index in range(num_chans): + task.ai_channels.add_ai_voltage_chan( + f"{real_x_series_multiplexed_device.name}/_ao{chan_index}_vs_aognd", + min_val=-10, + max_val=10, + ) + + # we'll be doing simple on-demand, so start the task now + task.start() + + return task + + +@pytest.fixture +def do_single_line_task( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a single-line DO task.""" + task = generate_task() + task.do_channels.add_do_chan( + real_x_series_device.do_lines[0].name, line_grouping=LineGrouping.CHAN_FOR_ALL_LINES + ) + _start_do_task(task) + return task + + +@pytest.fixture +def do_single_channel_multi_line_task( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a single-channel DO task.""" + task = generate_task() + chan = task.do_channels.add_do_chan( + flatten_channel_string(real_x_series_device.do_lines.channel_names[:8]), + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + _start_do_task(task, num_chans=chan.do_num_lines) + return task + + +@pytest.fixture +def do_multi_channel_multi_line_task( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a multi-channel DO task.""" + task = generate_task() + task.do_channels.add_do_chan( + flatten_channel_string(real_x_series_device.do_lines.channel_names[:8]), + line_grouping=LineGrouping.CHAN_PER_LINE, + ) + _start_do_task(task, num_chans=task.number_of_channels) + return task + + +@pytest.fixture +def do_port0_task( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a single-channel DO task.""" + task = generate_task() + # X Series port 0 has either 32 or 8 lines. The former can only be used with 32-bit writes. The + # latter can be used with any sized port write. + task.do_channels.add_do_chan( + real_x_series_device.do_ports[0].name, + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + _start_do_task(task, is_port=True) + return task + + +@pytest.fixture +def do_port1_task( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a single-channel DO task.""" + task = generate_task() + # X Series port 1 has 8 lines, and can be used with any sized port write. + task.do_channels.add_do_chan( + real_x_series_device.do_ports[1].name, + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + _start_do_task(task, is_port=True) + return task + + +@pytest.fixture +def do_multi_channel_port_task( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a multi-channel DO task.""" + task = generate_task() + # X Series port 1 has 8 lines, and can be used with any sized port write + task.do_channels.add_do_chan( + real_x_series_device.do_ports[1].name, + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + # X Series port 2 has 8 lines, and can be used with any sized port write + task.do_channels.add_do_chan( + real_x_series_device.do_ports[2].name, + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + _start_do_task(task, is_port=True, num_chans=task.number_of_channels) + return task + + +@pytest.fixture +def di_single_line_loopback_task( + generate_task: Callable[[], nidaqmx.Task], + real_x_series_device: nidaqmx.system.Device, +) -> nidaqmx.Task: + """Configure a single-line DI loopback task.""" + task = generate_task() + task.di_channels.add_di_chan( + real_x_series_device.di_lines[0].name, line_grouping=LineGrouping.CHAN_FOR_ALL_LINES + ) + _start_di_task(task) + return task + + +@pytest.fixture +def di_multi_line_loopback_task( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a multi-line DI loopback task.""" + task = generate_task() + task.di_channels.add_di_chan( + flatten_channel_string(real_x_series_device.di_lines.channel_names[:8]), + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + _start_di_task(task) + return task + + +@pytest.fixture +def di_port0_loopback_task( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a single-channel DI loopback task.""" + task = generate_task() + task.di_channels.add_di_chan( + real_x_series_device.di_ports[0].name, + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + _start_di_task(task) + return task + + +@pytest.fixture +def di_port0_loopback_task_32dio( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device_32dio: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a single-channel DI loopback task.""" + task = generate_task() + task.di_channels.add_di_chan( + real_x_series_device_32dio.di_ports[0].name, + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + _start_di_task(task) + return task + + +@pytest.fixture +def di_port1_loopback_task( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a single-channel DI loopback task.""" + task = generate_task() + task.di_channels.add_di_chan( + real_x_series_device.di_ports[1].name, + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + _start_di_task(task) + return task + + +@pytest.fixture +def di_port1_loopback_task_32dio( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device_32dio: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a single-channel DI loopback task.""" + task = generate_task() + task.di_channels.add_di_chan( + real_x_series_device_32dio.di_ports[1].name, + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + _start_di_task(task) + return task + + +@pytest.fixture +def di_port2_loopback_task( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a single-channel DI loopback task.""" + task = generate_task() + task.di_channels.add_di_chan( + real_x_series_device.di_ports[2].name, + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + _start_di_task(task) + return task + + +@pytest.fixture +def di_port2_loopback_task_32dio( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device_32dio: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a single-channel DI loopback task.""" + task = generate_task() + task.di_channels.add_di_chan( + real_x_series_device_32dio.di_ports[2].name, + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + _start_di_task(task) + return task + + +@pytest.fixture +def di_multi_channel_port_loopback_task( + generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device +) -> nidaqmx.Task: + """Configure a multi-channel DI loopback task.""" + task = generate_task() + task.di_channels.add_di_chan( + real_x_series_device.di_ports[1].name, + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + task.di_channels.add_di_chan( + real_x_series_device.di_ports[2].name, + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + _start_di_task(task) + return task diff --git a/tests/component/stream_readers/test_digital_multi_channel_reader.py b/tests/component/stream_readers/test_digital_multi_channel_reader.py index 83395ff48..d86dd9a18 100644 --- a/tests/component/stream_readers/test_digital_multi_channel_reader.py +++ b/tests/component/stream_readers/test_digital_multi_channel_reader.py @@ -19,10 +19,10 @@ from tests.component.conftest import ( _bool_array_to_int, _get_expected_data_for_line, - _get_expected_digital_data, + _get_digital_data, _get_expected_digital_port_data_port_major, _get_expected_digital_port_data_sample_major, - _get_num_lines_in_task, + _get_num_di_lines_in_task, _get_waveform_data, _is_timestamp_close_to_now, _read_and_copy, @@ -33,13 +33,13 @@ def test___digital_multi_channel_reader___read_one_sample_one_line___returns_val di_single_line_task: nidaqmx.Task, ) -> None: reader = DigitalMultiChannelReader(di_single_line_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_line_task) + num_lines = _get_num_di_lines_in_task(di_single_line_task) samples_to_read = 256 sample = numpy.full(num_lines, False, dtype=numpy.bool_) data = [_read_and_copy(reader.read_one_sample_one_line, sample) for _ in range(samples_to_read)] - assert [_bool_array_to_int(sample) for sample in data] == _get_expected_digital_data( + assert [_bool_array_to_int(sample) for sample in data] == _get_digital_data( num_lines, samples_to_read ) @@ -48,7 +48,7 @@ def test___digital_multi_channel_reader___read_one_sample_one_line_with_wrong_dt di_single_line_task: nidaqmx.Task, ) -> None: reader = DigitalMultiChannelReader(di_single_line_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_line_task) + num_lines = _get_num_di_lines_in_task(di_single_line_task) data = numpy.full(num_lines, math.inf, dtype=numpy.float64) with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: @@ -69,7 +69,7 @@ def test___digital_multi_channel_reader___read_one_sample_multi_line___returns_v _read_and_copy(reader.read_one_sample_multi_line, sample) for _ in range(samples_to_read) ] - assert [_bool_array_to_int(sample[:, 0]) for sample in data] == _get_expected_digital_data( + assert [_bool_array_to_int(sample[:, 0]) for sample in data] == _get_digital_data( num_channels, samples_to_read ) @@ -328,7 +328,7 @@ def test___digital_multi_channel_multi_line_reader___read_waveforms___returns_va ) -> None: reader = DigitalMultiChannelReader(di_multi_chan_multi_line_timing_task.in_stream) num_channels = di_multi_chan_multi_line_timing_task.number_of_channels - num_lines = _get_num_lines_in_task(di_multi_chan_multi_line_timing_task) + num_lines = _get_num_di_lines_in_task(di_multi_chan_multi_line_timing_task) samples_to_read = 10 waveforms = [DigitalWaveform(samples_to_read) for _ in range(num_channels)] @@ -354,7 +354,7 @@ def test___digital_multi_channel_different_lines_reader___read_waveforms___retur ) -> None: reader = DigitalMultiChannelReader(di_multi_chan_diff_lines_timing_task.in_stream) num_channels = di_multi_chan_diff_lines_timing_task.number_of_channels - num_lines = _get_num_lines_in_task(di_multi_chan_diff_lines_timing_task) + num_lines = _get_num_di_lines_in_task(di_multi_chan_diff_lines_timing_task) samples_to_read = 10 waveforms = [ DigitalWaveform(samples_to_read, 1), @@ -406,7 +406,7 @@ def test___digital_multi_channel_lines_and_port_reader___read_waveforms___return ) -> None: reader = DigitalMultiChannelReader(di_multi_chan_lines_and_port_task.in_stream) num_channels = di_multi_chan_lines_and_port_task.number_of_channels - num_lines = _get_num_lines_in_task(di_multi_chan_lines_and_port_task) + num_lines = _get_num_di_lines_in_task(di_multi_chan_lines_and_port_task) samples_to_read = 10 waveforms = [ DigitalWaveform(samples_to_read, 1), @@ -492,7 +492,7 @@ def test___digital_multi_channel_multi_line_reader___read_waveforms_no_args___re ) -> None: reader = DigitalMultiChannelReader(di_multi_chan_multi_line_timing_task.in_stream) num_channels = di_multi_chan_multi_line_timing_task.number_of_channels - num_lines = _get_num_lines_in_task(di_multi_chan_multi_line_timing_task) + num_lines = _get_num_di_lines_in_task(di_multi_chan_multi_line_timing_task) waveforms = [DigitalWaveform(50) for _ in range(num_channels)] samples_read = reader.read_waveforms(waveforms) @@ -516,7 +516,7 @@ def test___digital_multi_channel_multi_line_reader___read_waveforms_in_place___p ) -> None: reader = DigitalMultiChannelReader(di_multi_chan_multi_line_timing_task.in_stream) num_channels = di_multi_chan_multi_line_timing_task.number_of_channels - num_lines = _get_num_lines_in_task(di_multi_chan_multi_line_timing_task) + num_lines = _get_num_di_lines_in_task(di_multi_chan_multi_line_timing_task) samples_to_read = 10 waveforms = [DigitalWaveform(samples_to_read) for _ in range(num_channels)] @@ -614,7 +614,7 @@ def test___digital_multi_channel_multi_line_reader_with_timing_flag___read_wavef in_stream.waveform_attribute_mode = WaveformAttributeMode.TIMING reader = DigitalMultiChannelReader(in_stream) num_channels = di_multi_chan_multi_line_timing_task.number_of_channels - num_lines = _get_num_lines_in_task(di_multi_chan_multi_line_timing_task) + num_lines = _get_num_di_lines_in_task(di_multi_chan_multi_line_timing_task) samples_to_read = 10 waveforms = [DigitalWaveform(samples_to_read) for _ in range(num_channels)] @@ -642,7 +642,7 @@ def test___digital_multi_channel_multi_line_reader_with_extended_properties_flag in_stream.waveform_attribute_mode = WaveformAttributeMode.EXTENDED_PROPERTIES reader = DigitalMultiChannelReader(in_stream) num_channels = di_multi_chan_multi_line_timing_task.number_of_channels - num_lines = _get_num_lines_in_task(di_multi_chan_multi_line_timing_task) + num_lines = _get_num_di_lines_in_task(di_multi_chan_multi_line_timing_task) samples_to_read = 10 waveforms = [DigitalWaveform(samples_to_read) for _ in range(num_channels)] @@ -670,7 +670,7 @@ def test___digital_multi_channel_multi_line_reader_with_both_flags___read_wavefo ) reader = DigitalMultiChannelReader(in_stream) num_channels = di_multi_chan_multi_line_timing_task.number_of_channels - num_lines = _get_num_lines_in_task(di_multi_chan_multi_line_timing_task) + num_lines = _get_num_di_lines_in_task(di_multi_chan_multi_line_timing_task) samples_to_read = 10 waveforms = [DigitalWaveform(samples_to_read) for _ in range(num_channels)] @@ -698,7 +698,7 @@ def test___digital_multi_channel_multi_line_reader_with_none_flag___read_wavefor in_stream.waveform_attribute_mode = WaveformAttributeMode.NONE reader = DigitalMultiChannelReader(in_stream) num_channels = di_multi_chan_multi_line_timing_task.number_of_channels - num_lines = _get_num_lines_in_task(di_multi_chan_multi_line_timing_task) + num_lines = _get_num_di_lines_in_task(di_multi_chan_multi_line_timing_task) samples_to_read = 10 waveforms = [DigitalWaveform(samples_to_read) for _ in range(num_channels)] diff --git a/tests/component/stream_readers/test_digital_single_channel_reader.py b/tests/component/stream_readers/test_digital_single_channel_reader.py index 49b861e0b..a6382d266 100644 --- a/tests/component/stream_readers/test_digital_single_channel_reader.py +++ b/tests/component/stream_readers/test_digital_single_channel_reader.py @@ -19,8 +19,8 @@ from tests.component.conftest import ( _bool_array_to_int, _get_expected_data_for_line, - _get_expected_digital_data, - _get_num_lines_in_task, + _get_digital_data, + _get_num_di_lines_in_task, _get_waveform_data, _is_timestamp_close_to_now, _read_and_copy, @@ -31,19 +31,19 @@ def test___digital_single_channel_reader___read_one_sample_one_line___returns_va di_single_line_task: nidaqmx.Task, ) -> None: reader = DigitalSingleChannelReader(di_single_line_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_line_task) + num_lines = _get_num_di_lines_in_task(di_single_line_task) samples_to_read = 256 data = [reader.read_one_sample_one_line() for _ in range(samples_to_read)] - assert data == _get_expected_digital_data(num_lines, samples_to_read) + assert data == _get_digital_data(num_lines, samples_to_read) def test___digital_single_channel_reader___read_one_sample_multi_line___returns_valid_samples( di_single_channel_multi_line_task: nidaqmx.Task, ) -> None: reader = DigitalSingleChannelReader(di_single_channel_multi_line_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_channel_multi_line_task) + num_lines = _get_num_di_lines_in_task(di_single_channel_multi_line_task) samples_to_read = 256 sample = numpy.full(num_lines, False, dtype=numpy.bool_) @@ -51,7 +51,7 @@ def test___digital_single_channel_reader___read_one_sample_multi_line___returns_ _read_and_copy(reader.read_one_sample_multi_line, sample) for _ in range(samples_to_read) ] - assert [_bool_array_to_int(sample) for sample in data] == _get_expected_digital_data( + assert [_bool_array_to_int(sample) for sample in data] == _get_digital_data( num_lines, samples_to_read ) @@ -60,7 +60,7 @@ def test___digital_single_channel_reader___read_one_sample_multi_line_with_wrong di_single_channel_multi_line_task: nidaqmx.Task, ) -> None: reader = DigitalSingleChannelReader(di_single_channel_multi_line_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_channel_multi_line_task) + num_lines = _get_num_di_lines_in_task(di_single_channel_multi_line_task) data = numpy.full(num_lines, math.inf, dtype=numpy.float64) with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: @@ -73,43 +73,43 @@ def test___digital_single_channel_reader___read_one_sample_port_byte___returns_v di_single_channel_port_byte_task: nidaqmx.Task, ) -> None: reader = DigitalSingleChannelReader(di_single_channel_port_byte_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_channel_port_byte_task) + num_lines = _get_num_di_lines_in_task(di_single_channel_port_byte_task) samples_to_read = 256 data = [reader.read_one_sample_port_byte() for _ in range(samples_to_read)] - assert data == _get_expected_digital_data(num_lines, samples_to_read) + assert data == _get_digital_data(num_lines, samples_to_read) def test___digital_single_channel_reader___read_one_sample_port_uint16___returns_valid_samples( di_single_channel_port_uint16_task: nidaqmx.Task, ) -> None: reader = DigitalSingleChannelReader(di_single_channel_port_uint16_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_channel_port_uint16_task) + num_lines = _get_num_di_lines_in_task(di_single_channel_port_uint16_task) samples_to_read = 256 data = [reader.read_one_sample_port_uint16() for _ in range(samples_to_read)] - assert data == _get_expected_digital_data(num_lines, samples_to_read) + assert data == _get_digital_data(num_lines, samples_to_read) def test___digital_single_channel_reader___read_one_sample_port_uint32___returns_valid_samples( di_single_channel_port_uint32_task: nidaqmx.Task, ) -> None: reader = DigitalSingleChannelReader(di_single_channel_port_uint32_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_channel_port_uint32_task) + num_lines = _get_num_di_lines_in_task(di_single_channel_port_uint32_task) samples_to_read = 256 data = [reader.read_one_sample_port_uint32() for _ in range(samples_to_read)] - assert data == _get_expected_digital_data(num_lines, samples_to_read) + assert data == _get_digital_data(num_lines, samples_to_read) def test___digital_single_channel_reader___read_many_sample_port_byte___returns_valid_samples( di_single_channel_port_byte_task: nidaqmx.Task, ) -> None: reader = DigitalSingleChannelReader(di_single_channel_port_byte_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_channel_port_byte_task) + num_lines = _get_num_di_lines_in_task(di_single_channel_port_byte_task) samples_to_read = 256 data = numpy.full(samples_to_read, numpy.iinfo(numpy.uint8).min, dtype=numpy.uint8) @@ -118,7 +118,7 @@ def test___digital_single_channel_reader___read_many_sample_port_byte___returns_ ) assert samples_read == samples_to_read - assert data.tolist() == _get_expected_digital_data(num_lines, samples_to_read) + assert data.tolist() == _get_digital_data(num_lines, samples_to_read) def test___digital_single_channel_reader___read_many_sample_port_byte_with_wrong_dtype___raises_error_with_correct_dtype( @@ -138,7 +138,7 @@ def test___digital_single_channel_reader___read_many_sample_port_uint16___return di_single_channel_port_uint16_task: nidaqmx.Task, ) -> None: reader = DigitalSingleChannelReader(di_single_channel_port_uint16_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_channel_port_uint16_task) + num_lines = _get_num_di_lines_in_task(di_single_channel_port_uint16_task) samples_to_read = 256 data = numpy.full(samples_to_read, numpy.iinfo(numpy.uint16).min, dtype=numpy.uint16) @@ -147,7 +147,7 @@ def test___digital_single_channel_reader___read_many_sample_port_uint16___return ) assert samples_read == samples_to_read - assert data.tolist() == _get_expected_digital_data(num_lines, samples_to_read) + assert data.tolist() == _get_digital_data(num_lines, samples_to_read) def test___digital_single_channel_reader___read_many_sample_port_uint16_with_wrong_dtype___raises_error_with_correct_dtype( @@ -167,7 +167,7 @@ def test___digital_single_channel_reader___read_many_sample_port_uint32___return di_single_channel_port_uint32_task: nidaqmx.Task, ) -> None: reader = DigitalSingleChannelReader(di_single_channel_port_uint32_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_channel_port_uint32_task) + num_lines = _get_num_di_lines_in_task(di_single_channel_port_uint32_task) samples_to_read = 256 data = numpy.full(samples_to_read, numpy.iinfo(numpy.uint32).min, dtype=numpy.uint32) @@ -176,7 +176,7 @@ def test___digital_single_channel_reader___read_many_sample_port_uint32___return ) assert samples_read == samples_to_read - assert data.tolist() == _get_expected_digital_data(num_lines, samples_to_read) + assert data.tolist() == _get_digital_data(num_lines, samples_to_read) def test___digital_single_channel_reader___read_many_sample_port_uint32_with_wrong_dtype___raises_error_with_correct_dtype( @@ -218,7 +218,7 @@ def test___digital_single_line_reader___read_waveform___returns_valid_waveform( samples_read = reader.read_waveform(waveform, samples_to_read) assert samples_read == samples_to_read - assert _get_waveform_data(waveform) == _get_expected_digital_data(1, samples_to_read) + assert _get_waveform_data(waveform) == _get_digital_data(1, samples_to_read) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) assert waveform.timing.sample_interval_mode == SampleIntervalMode.REGULAR @@ -231,13 +231,13 @@ def test___digital_single_channel_multi_line_reader___read_waveform___returns_va ) -> None: reader = DigitalSingleChannelReader(di_single_channel_multi_line_timing_task.in_stream) samples_to_read = 10 - num_lines = _get_num_lines_in_task(di_single_channel_multi_line_timing_task) + num_lines = _get_num_di_lines_in_task(di_single_channel_multi_line_timing_task) waveform = DigitalWaveform(samples_to_read, num_lines) samples_read = reader.read_waveform(waveform, samples_to_read) assert samples_read == samples_to_read - assert _get_waveform_data(waveform) == _get_expected_digital_data(num_lines, samples_to_read) + assert _get_waveform_data(waveform) == _get_digital_data(num_lines, samples_to_read) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) assert waveform.timing.sample_interval_mode == SampleIntervalMode.REGULAR @@ -254,7 +254,7 @@ def test___digital_single_line_reader___read_waveform_no_args___returns_valid_wa samples_read = reader.read_waveform(waveform) assert samples_read == 50 - assert _get_waveform_data(waveform) == _get_expected_digital_data(1, 50) + assert _get_waveform_data(waveform) == _get_digital_data(1, 50) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) assert waveform.timing.sample_interval_mode == SampleIntervalMode.REGULAR @@ -266,13 +266,13 @@ def test___digital_single_channel_multi_line_reader___read_waveform_no_args___re di_single_channel_multi_line_timing_task: nidaqmx.Task, ) -> None: reader = DigitalSingleChannelReader(di_single_channel_multi_line_timing_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_channel_multi_line_timing_task) + num_lines = _get_num_di_lines_in_task(di_single_channel_multi_line_timing_task) waveform = DigitalWaveform(50, num_lines) samples_read = reader.read_waveform(waveform) assert samples_read == 50 - assert _get_waveform_data(waveform) == _get_expected_digital_data(num_lines, 50) + assert _get_waveform_data(waveform) == _get_digital_data(num_lines, 50) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) assert waveform.timing.sample_interval_mode == SampleIntervalMode.REGULAR @@ -289,7 +289,7 @@ def test___digital_single_line_reader___read_waveform_in_place___returns_valid_w samples_read = reader.read_waveform(waveform) assert samples_read == 50 - assert _get_waveform_data(waveform) == _get_expected_digital_data(1, 50) + assert _get_waveform_data(waveform) == _get_digital_data(1, 50) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) assert waveform.channel_name == di_single_line_timing_task.di_channels[0].name @@ -300,13 +300,13 @@ def test___digital_single_channel_multi_line_reader___read_waveform_in_place___r di_single_channel_multi_line_timing_task: nidaqmx.Task, ) -> None: reader = DigitalSingleChannelReader(di_single_channel_multi_line_timing_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_channel_multi_line_timing_task) + num_lines = _get_num_di_lines_in_task(di_single_channel_multi_line_timing_task) waveform = DigitalWaveform(sample_count=50, signal_count=8) samples_read = reader.read_waveform(waveform) assert samples_read == 50 - assert _get_waveform_data(waveform) == _get_expected_digital_data(num_lines, 50) + assert _get_waveform_data(waveform) == _get_digital_data(num_lines, 50) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) assert waveform.channel_name == di_single_channel_multi_line_timing_task.di_channels[0].name @@ -407,7 +407,7 @@ def test___digital_single_line_reader___read_waveform_high_sample_rate___returns samples_read = reader.read_waveform(waveform, samples_to_read) assert samples_read == samples_to_read - assert _get_waveform_data(waveform) == _get_expected_digital_data(1, 50) + assert _get_waveform_data(waveform) == _get_digital_data(1, 50) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 10_000_000) assert waveform.sample_count == samples_to_read @@ -426,7 +426,7 @@ def test___digital_single_line_reader_with_timing_flag___read_waveform___only_in samples_read = reader.read_waveform(waveform) assert samples_read == 50 - assert _get_waveform_data(waveform) == _get_expected_digital_data(1, 50) + assert _get_waveform_data(waveform) == _get_digital_data(1, 50) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval_mode == SampleIntervalMode.REGULAR assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) @@ -445,7 +445,7 @@ def test___digital_single_line_reader_with_extended_properties_flag___read_wavef samples_read = reader.read_waveform(waveform) assert samples_read == 50 - assert _get_waveform_data(waveform) == _get_expected_digital_data(1, 50) + assert _get_waveform_data(waveform) == _get_digital_data(1, 50) assert waveform.timing.sample_interval_mode == SampleIntervalMode.NONE assert waveform.channel_name == di_single_line_timing_task.di_channels[0].name @@ -464,7 +464,7 @@ def test___digital_single_line_reader_with_both_flags___read_waveform___includes samples_read = reader.read_waveform(waveform) assert samples_read == 50 - assert _get_waveform_data(waveform) == _get_expected_digital_data(1, 50) + assert _get_waveform_data(waveform) == _get_digital_data(1, 50) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval_mode == SampleIntervalMode.REGULAR assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) @@ -483,7 +483,7 @@ def test___digital_single_line_reader_with_none_flag___read_waveform___minimal_w samples_read = reader.read_waveform(waveform) assert samples_read == 50 - assert _get_waveform_data(waveform) == _get_expected_digital_data(1, 50) + assert _get_waveform_data(waveform) == _get_digital_data(1, 50) assert waveform.timing.sample_interval_mode == SampleIntervalMode.NONE assert waveform.channel_name == "" @@ -497,14 +497,14 @@ def test___digital_single_channel_port_uint32_reader___read_waveform___returns_v di_single_channel_port_uint32_timing_task: nidaqmx.Task, ) -> None: reader = DigitalSingleChannelReader(di_single_channel_port_uint32_timing_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_channel_port_uint32_timing_task) + num_lines = _get_num_di_lines_in_task(di_single_channel_port_uint32_timing_task) samples_to_read = 10 waveform = DigitalWaveform(samples_to_read, num_lines) samples_read = reader.read_waveform(waveform, samples_to_read) assert samples_read == 50 - assert _get_waveform_data(waveform) == _get_expected_digital_data(num_lines, samples_to_read) + assert _get_waveform_data(waveform) == _get_digital_data(num_lines, samples_to_read) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) assert waveform.timing.sample_interval_mode == SampleIntervalMode.REGULAR @@ -517,7 +517,7 @@ def test___digital_single_channel_lines_and_port___read_waveform___returns_valid sim_6363_device: nidaqmx.system.Device, ) -> None: reader = DigitalSingleChannelReader(di_single_chan_lines_and_port_task.in_stream) - num_lines = _get_num_lines_in_task(di_single_chan_lines_and_port_task) + num_lines = _get_num_di_lines_in_task(di_single_chan_lines_and_port_task) samples_to_read = 10 waveform = DigitalWaveform(samples_to_read, num_lines) diff --git a/tests/component/stream_writers/test_analog_multi_channel_writer.py b/tests/component/stream_writers/test_analog_multi_channel_writer.py new file mode 100644 index 000000000..fb660d3f1 --- /dev/null +++ b/tests/component/stream_writers/test_analog_multi_channel_writer.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +import ctypes + +import numpy +import pytest + +import nidaqmx +from nidaqmx.stream_writers import AnalogMultiChannelWriter +from tests.component.conftest import ( + _get_expected_voltage_for_chan, + VOLTAGE_EPSILON, +) + + +def test___analog_multi_channel_writer___write_one_sample___updates_output( + ao_multi_channel_task: nidaqmx.Task, + ai_multi_channel_loopback_task: nidaqmx.Task, +) -> None: + writer = AnalogMultiChannelWriter(ao_multi_channel_task.out_stream) + num_channels = ao_multi_channel_task.number_of_channels + expected = [_get_expected_voltage_for_chan(chan_index) for chan_index in range(num_channels)] + data = numpy.asarray(expected, dtype=numpy.float64) + + writer.write_one_sample(data) + + assert ai_multi_channel_loopback_task.read() == pytest.approx(expected, abs=VOLTAGE_EPSILON) + + +def test___analog_multi_channel_writer___write_one_sample_with_wrong_dtype___raises_error_with_correct_dtype( + ao_multi_channel_task: nidaqmx.Task, +) -> None: + writer = AnalogMultiChannelWriter(ao_multi_channel_task.out_stream) + num_channels = ao_multi_channel_task.number_of_channels + data = numpy.full(num_channels, 0.0, dtype=numpy.float32) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + writer.write_one_sample(data) + + assert "float64" in exc_info.value.args[0] + + +def test___analog_multi_channel_writer___write_many_sample___updates_output( + ao_multi_channel_task: nidaqmx.Task, + ai_multi_channel_loopback_task: nidaqmx.Task, +) -> None: + writer = AnalogMultiChannelWriter(ao_multi_channel_task.out_stream) + num_channels = ao_multi_channel_task.number_of_channels + samples_to_write = 10 + expected = [_get_expected_voltage_for_chan(chan_index) for chan_index in range(num_channels)] + # sweep up to the expected values, the only one we'll validate + data = numpy.ascontiguousarray( + numpy.transpose( + numpy.linspace( + [0.0] * num_channels, + expected, + num=samples_to_write, + dtype=numpy.float64, + ) + ) + ) + + samples_written = writer.write_many_sample(data) + + assert samples_written == samples_to_write + assert ai_multi_channel_loopback_task.read() == pytest.approx(expected, abs=VOLTAGE_EPSILON) + + +def test___analog_multi_channel_writer___write_many_sample_with_wrong_dtype___raises_error_with_correct_dtype( + ao_multi_channel_task: nidaqmx.Task, +) -> None: + writer = AnalogMultiChannelWriter(ao_multi_channel_task.out_stream) + num_channels = ao_multi_channel_task.number_of_channels + samples_to_write = 10 + data = numpy.full((num_channels, samples_to_write), 0.0, dtype=numpy.float32) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + _ = writer.write_many_sample(data) + + assert "float64" in exc_info.value.args[0] diff --git a/tests/component/stream_writers/test_analog_single_channel_writer.py b/tests/component/stream_writers/test_analog_single_channel_writer.py new file mode 100644 index 000000000..063d15d6d --- /dev/null +++ b/tests/component/stream_writers/test_analog_single_channel_writer.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +import ctypes + +import numpy +import pytest + +import nidaqmx +from nidaqmx.stream_writers import AnalogSingleChannelWriter +from tests.component.conftest import ( + _get_expected_voltage_for_chan, + VOLTAGE_EPSILON, +) + + +def test___analog_single_channel_writer___write_one_sample___updates_output( + ao_single_channel_task: nidaqmx.Task, + ai_single_channel_loopback_task: nidaqmx.Task, +) -> None: + writer = AnalogSingleChannelWriter(ao_single_channel_task.out_stream) + expected = _get_expected_voltage_for_chan(0) + + writer.write_one_sample(expected) + + assert ai_single_channel_loopback_task.read() == pytest.approx(expected, abs=VOLTAGE_EPSILON) + + +def test___analog_single_channel_writer___write_many_sample___updates_output( + ao_single_channel_task: nidaqmx.Task, + ai_single_channel_loopback_task: nidaqmx.Task, +) -> None: + writer = AnalogSingleChannelWriter(ao_single_channel_task.out_stream) + samples_to_write = 10 + expected = _get_expected_voltage_for_chan(0) + # sweep up to the expected value, the only one we'll validate + data = numpy.linspace(0.0, expected, num=samples_to_write, dtype=numpy.float64) + + samples_written = writer.write_many_sample(data) + + assert samples_written == samples_to_write + assert ai_single_channel_loopback_task.read() == pytest.approx(expected, abs=VOLTAGE_EPSILON) + + +def test___analog_single_channel_writer___write_many_sample_with_wrong_dtype___raises_error_with_correct_dtype( + ao_single_channel_task: nidaqmx.Task, +) -> None: + writer = AnalogSingleChannelWriter(ao_single_channel_task.out_stream) + samples_to_write = 10 + expected = _get_expected_voltage_for_chan(0) + data = numpy.full(samples_to_write, expected, dtype=numpy.float32) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + _ = writer.write_many_sample(data) + + assert "float64" in exc_info.value.args[0] diff --git a/tests/component/stream_writers/test_analog_unscaled_writer.py b/tests/component/stream_writers/test_analog_unscaled_writer.py new file mode 100644 index 000000000..da6525439 --- /dev/null +++ b/tests/component/stream_writers/test_analog_unscaled_writer.py @@ -0,0 +1,182 @@ +from __future__ import annotations + +import ctypes + +import numpy +import pytest + +import nidaqmx +from nidaqmx.stream_writers import AnalogUnscaledWriter +from tests.component.conftest import ( + _get_expected_voltage_for_chan, + _volts_to_codes, + VOLTAGE_EPSILON_FOR_RAW, +) + + +def test___analog_unscaled_writer___write_int16___updates_output( + ao_multi_channel_task: nidaqmx.Task, + ai_multi_channel_loopback_task: nidaqmx.Task, +) -> None: + writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) + num_channels = ao_multi_channel_task.number_of_channels + samples_to_write = 10 + expected = [_get_expected_voltage_for_chan(chan_index) for chan_index in range(num_channels)] + # sweep up to the expected values, the only one we'll validate + data = numpy.ascontiguousarray( + numpy.transpose( + numpy.linspace( + [0] * num_channels, + [_volts_to_codes(v) for v in expected], + num=samples_to_write, + dtype=numpy.int16, + ) + ) + ) + + samples_written = writer.write_int16(data) + + assert samples_written == samples_to_write + assert ai_multi_channel_loopback_task.read() == pytest.approx( + expected, abs=VOLTAGE_EPSILON_FOR_RAW + ) + + +def test___analog_unscaled_writer___write_int16_with_wrong_dtype___raises_error_with_correct_dtype( + ao_multi_channel_task: nidaqmx.Task, +) -> None: + writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) + num_channels = ao_multi_channel_task.number_of_channels + samples_to_write = 10 + data = numpy.full((num_channels, samples_to_write), 0.0, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + _ = writer.write_int16(data) + + assert "int16" in exc_info.value.args[0] + + +def test___analog_unscaled_writer___write_int32___updates_output( + ao_multi_channel_task: nidaqmx.Task, + ai_multi_channel_loopback_task: nidaqmx.Task, +) -> None: + writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) + num_channels = ao_multi_channel_task.number_of_channels + samples_to_write = 10 + expected = [_get_expected_voltage_for_chan(chan_index) for chan_index in range(num_channels)] + # sweep up to the expected values, the only one we'll validate + data = numpy.ascontiguousarray( + numpy.transpose( + numpy.linspace( + [0] * num_channels, + [_volts_to_codes(v) for v in expected], + num=samples_to_write, + dtype=numpy.int32, + ) + ) + ) + + samples_written = writer.write_int32(data) + + assert samples_written == samples_to_write + assert ai_multi_channel_loopback_task.read() == pytest.approx( + expected, abs=VOLTAGE_EPSILON_FOR_RAW + ) + + +def test___analog_unscaled_writer___write_int32_with_wrong_dtype___raises_error_with_correct_dtype( + ao_multi_channel_task: nidaqmx.Task, +) -> None: + writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) + num_channels = ao_multi_channel_task.number_of_channels + samples_to_write = 10 + data = numpy.full((num_channels, samples_to_write), 0.0, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + _ = writer.write_int32(data) + + assert "int32" in exc_info.value.args[0] + + +def test___analog_unscaled_writer___write_uint16___updates_output( + ao_multi_channel_task: nidaqmx.Task, + ai_multi_channel_loopback_task: nidaqmx.Task, +) -> None: + writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) + num_channels = ao_multi_channel_task.number_of_channels + samples_to_write = 10 + expected = [_get_expected_voltage_for_chan(chan_index) for chan_index in range(num_channels)] + # sweep up to the expected values, the only one we'll validate + data = numpy.ascontiguousarray( + numpy.transpose( + numpy.linspace( + [0] * num_channels, + [_volts_to_codes(v) for v in expected], + num=samples_to_write, + dtype=numpy.uint16, + ) + ) + ) + + samples_written = writer.write_uint16(data) + + assert samples_written == samples_to_write + assert ai_multi_channel_loopback_task.read() == pytest.approx( + expected, abs=VOLTAGE_EPSILON_FOR_RAW + ) + + +def test___analog_unscaled_writer___write_uint16_with_wrong_dtype___raises_error_with_correct_dtype( + ao_multi_channel_task: nidaqmx.Task, +) -> None: + writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) + num_channels = ao_multi_channel_task.number_of_channels + samples_to_write = 10 + data = numpy.full((num_channels, samples_to_write), 0.0, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + _ = writer.write_uint16(data) + + assert "uint16" in exc_info.value.args[0] + + +def test___analog_unscaled_writer___write_uint32___updates_output( + ao_multi_channel_task: nidaqmx.Task, + ai_multi_channel_loopback_task: nidaqmx.Task, +) -> None: + writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) + num_channels = ao_multi_channel_task.number_of_channels + samples_to_write = 10 + expected = [_get_expected_voltage_for_chan(chan_index) for chan_index in range(num_channels)] + # sweep up to the expected values, the only one we'll validate + data = numpy.ascontiguousarray( + numpy.transpose( + numpy.linspace( + [0] * num_channels, + [_volts_to_codes(v) for v in expected], + num=samples_to_write, + dtype=numpy.uint32, + ) + ) + ) + + samples_written = writer.write_uint32(data) + + assert samples_written == samples_to_write + assert ai_multi_channel_loopback_task.read() == pytest.approx( + expected, abs=VOLTAGE_EPSILON_FOR_RAW + ) + + +def test___analog_unscaled_writer___write_uint32_with_wrong_dtype___raises_error_with_correct_dtype( + ao_multi_channel_task: nidaqmx.Task, +) -> None: + writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) + num_channels = ao_multi_channel_task.number_of_channels + samples_to_write = 10 + data = numpy.full((num_channels, samples_to_write), 0.0, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + _ = writer.write_uint32(data) + + assert "uint32" in exc_info.value.args[0] diff --git a/tests/component/stream_writers/test_digital_multi_channel_writer.py b/tests/component/stream_writers/test_digital_multi_channel_writer.py new file mode 100644 index 000000000..f2569727d --- /dev/null +++ b/tests/component/stream_writers/test_digital_multi_channel_writer.py @@ -0,0 +1,265 @@ +from __future__ import annotations + +import ctypes +import math +from typing import Callable + +import numpy +import pytest + +import nidaqmx +import nidaqmx.system +from nidaqmx.constants import LineGrouping +from nidaqmx.stream_writers import DigitalMultiChannelWriter +from tests.component.conftest import ( + _get_digital_data, + _get_digital_port_data_for_sample, + _get_digital_port_data_port_major, + _get_digital_port_data_sample_major, + _get_num_do_lines_in_task, + _int_to_bool_array, + _start_do_task, +) + + +def test___digital_multi_channel_writer___write_one_sample_one_line___updates_output( + do_single_line_task: nidaqmx.Task, + di_single_line_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_single_line_task.out_stream) + num_lines = _get_num_do_lines_in_task(do_single_line_task) + samples_to_write = 256 + + # "sweep" up to the final value, the only one we'll validate + for datum in _get_digital_data(num_lines, samples_to_write): + writer.write_one_sample_one_line(_int_to_bool_array(num_lines, datum)) + + assert di_single_line_loopback_task.read() == datum + + +def test___digital_multi_channel_writer___write_one_sample_multi_line___updates_output( + do_multi_channel_multi_line_task: nidaqmx.Task, + di_multi_line_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_multi_line_task.out_stream) + num_channels = do_multi_channel_multi_line_task.number_of_channels + samples_to_write = 256 + + # "sweep" up to the final value, the only one we'll validate + for datum in _get_digital_data(num_channels, samples_to_write): + data_to_write = _int_to_bool_array(num_channels, datum).reshape((num_channels, 1)) + writer.write_one_sample_multi_line(data_to_write) + + assert di_multi_line_loopback_task.read() == datum + + +def test___digital_multi_channel_writer___write_one_sample_multi_line_jagged___updates_output( + di_port0_loopback_task_32dio: nidaqmx.Task, + di_port1_loopback_task_32dio: nidaqmx.Task, + di_port2_loopback_task_32dio: nidaqmx.Task, + generate_task: Callable[[], nidaqmx.Task], + real_x_series_device_32dio: nidaqmx.system.Device, +) -> None: + task = generate_task() + for port in real_x_series_device_32dio.do_ports: + task.do_channels.add_do_chan( + port.name, + line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, + ) + _start_do_task(task, is_port=True, num_chans=task.number_of_channels) + writer = DigitalMultiChannelWriter(task.out_stream) + num_channels = task.number_of_channels + samples_to_write = 0xA5 + + # "sweep" up to the final value, the only one we'll validate + for datum in _get_digital_data(num_channels * 32, samples_to_write): + data_to_write = _int_to_bool_array(num_channels * 32, datum).reshape((num_channels, 32)) + writer.write_one_sample_multi_line(data_to_write) + + assert di_port0_loopback_task_32dio.read() == datum & 0xFFFFFFFF + assert di_port1_loopback_task_32dio.read() == (datum >> 32) & 0xFF + assert di_port2_loopback_task_32dio.read() == (datum >> 64) & 0xFF + + +def test___digital_multi_channel_writer___write_one_sample_multi_line_with_wrong_dtype___raises_error_with_correct_dtype( + do_multi_channel_multi_line_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_multi_line_task.out_stream) + num_channels = do_multi_channel_multi_line_task.number_of_channels + sample = numpy.full((num_channels, 1), math.inf, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + writer.write_one_sample_multi_line(sample) + + assert "bool" in exc_info.value.args[0] + + +def test___digital_multi_channel_writer___write_one_sample_port_byte___updates_output( + do_multi_channel_port_task: nidaqmx.Task, + di_multi_channel_port_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) + samples_to_write = 256 + + # "sweep" up to the final value, the only one we'll validate + for datum in _get_digital_port_data_sample_major(do_multi_channel_port_task, samples_to_write): + writer.write_one_sample_port_byte(numpy.array(datum, dtype=numpy.uint8)) + + assert di_multi_channel_port_loopback_task.read() == datum + + +def test___digital_multi_channel_writer___write_one_sample_port_byte_with_wrong_dtype___raises_error_with_correct_dtype( + do_multi_channel_port_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) + num_channels = do_multi_channel_port_task.number_of_channels + data = numpy.full(num_channels, math.inf, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + writer.write_one_sample_port_byte(data) + + assert "uint8" in exc_info.value.args[0] + + +def test___digital_multi_channel_writer___write_many_sample_port_byte___updates_output( + do_multi_channel_port_task: nidaqmx.Task, + di_multi_channel_port_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) + samples_to_write = 256 + + # "sweep" up to the final value, the only one we'll validate + data = _get_digital_port_data_port_major(do_multi_channel_port_task, samples_to_write) + writer.write_many_sample_port_byte(numpy.array(data, dtype=numpy.uint8)) + + assert di_multi_channel_port_loopback_task.read() == _get_digital_port_data_for_sample( + do_multi_channel_port_task, samples_to_write - 1 + ) + + +def test___digital_multi_channel_writer___write_many_sample_port_byte_with_wrong_dtype___raises_error_with_correct_dtype( + do_multi_channel_port_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) + num_channels = do_multi_channel_port_task.number_of_channels + samples_to_write = 256 + data = numpy.full((num_channels, samples_to_write), math.inf, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + writer.write_many_sample_port_byte(data) + + assert "uint8" in exc_info.value.args[0] + + +def test___digital_multi_channel_writer___write_one_sample_port_uint16___updates_output( + do_multi_channel_port_task: nidaqmx.Task, + di_multi_channel_port_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) + samples_to_write = 256 + + # "sweep" up to the final value, the only one we'll validate + for datum in _get_digital_port_data_sample_major(do_multi_channel_port_task, samples_to_write): + writer.write_one_sample_port_uint16(numpy.array(datum, dtype=numpy.uint16)) + + assert di_multi_channel_port_loopback_task.read() == datum + + +def test___digital_multi_channel_writer___write_one_sample_port_uint16_with_wrong_dtype___raises_error_with_correct_dtype( + do_multi_channel_port_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) + num_channels = do_multi_channel_port_task.number_of_channels + data = numpy.full(num_channels, math.inf, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + writer.write_one_sample_port_uint16(data) + + assert "uint16" in exc_info.value.args[0] + + +def test___digital_multi_channel_writer___write_many_sample_port_uint16___updates_output( + do_multi_channel_port_task: nidaqmx.Task, + di_multi_channel_port_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) + samples_to_write = 256 + + # "sweep" up to the final value, the only one we'll validate + data = _get_digital_port_data_port_major(do_multi_channel_port_task, samples_to_write) + writer.write_many_sample_port_uint16(numpy.array(data, dtype=numpy.uint16)) + + assert di_multi_channel_port_loopback_task.read() == _get_digital_port_data_for_sample( + do_multi_channel_port_task, samples_to_write - 1 + ) + + +def test___digital_multi_channel_writer___write_many_sample_port_uint16_with_wrong_dtype___raises_error_with_correct_dtype( + do_multi_channel_port_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) + num_channels = do_multi_channel_port_task.number_of_channels + samples_to_write = 256 + data = numpy.full((num_channels, samples_to_write), math.inf, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + writer.write_many_sample_port_uint16(data) + + assert "uint16" in exc_info.value.args[0] + + +def test___digital_multi_channel_writer___write_one_sample_port_uint32___updates_output( + do_multi_channel_port_task: nidaqmx.Task, + di_multi_channel_port_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) + samples_to_write = 256 + + # "sweep" up to the final value, the only one we'll validate + for datum in _get_digital_port_data_sample_major(do_multi_channel_port_task, samples_to_write): + writer.write_one_sample_port_uint32(numpy.array(datum, dtype=numpy.uint32)) + + assert di_multi_channel_port_loopback_task.read() == datum + + +def test___digital_multi_channel_writer___write_one_sample_port_uint32_with_wrong_dtype___raises_error_with_correct_dtype( + do_multi_channel_port_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) + num_channels = do_multi_channel_port_task.number_of_channels + data = numpy.full(num_channels, math.inf, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + writer.write_one_sample_port_uint32(data) + + assert "uint32" in exc_info.value.args[0] + + +def test___digital_multi_channel_writer___write_many_sample_port_uint32___updates_output( + do_multi_channel_port_task: nidaqmx.Task, + di_multi_channel_port_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) + samples_to_write = 256 + + # "sweep" up to the final value, the only one we'll validate + data = _get_digital_port_data_port_major(do_multi_channel_port_task, samples_to_write) + writer.write_many_sample_port_uint32(numpy.array(data, dtype=numpy.uint32)) + + assert di_multi_channel_port_loopback_task.read() == _get_digital_port_data_for_sample( + do_multi_channel_port_task, samples_to_write - 1 + ) + + +def test___digital_multi_channel_writer___write_many_sample_port_uint32_with_wrong_dtype___raises_error_with_correct_dtype( + do_multi_channel_port_task: nidaqmx.Task, +) -> None: + writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) + num_channels = do_multi_channel_port_task.number_of_channels + samples_to_write = 256 + data = numpy.full((num_channels, samples_to_write), math.inf, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + writer.write_many_sample_port_uint32(data) + + assert "uint32" in exc_info.value.args[0] diff --git a/tests/component/stream_writers/test_digital_single_channel_writer.py b/tests/component/stream_writers/test_digital_single_channel_writer.py new file mode 100644 index 000000000..adfa713b5 --- /dev/null +++ b/tests/component/stream_writers/test_digital_single_channel_writer.py @@ -0,0 +1,184 @@ +from __future__ import annotations + +import ctypes +import math + +import numpy +import pytest + +import nidaqmx +from nidaqmx.stream_writers import DigitalSingleChannelWriter +from tests.component.conftest import ( + _get_digital_data, + _get_num_do_lines_in_task, + _int_to_bool_array, +) + + +def test___digital_single_channel_writer___write_one_sample_one_line___updates_output( + do_single_line_task: nidaqmx.Task, + di_single_line_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalSingleChannelWriter(do_single_line_task.out_stream) + sample = True + + writer.write_one_sample_one_line(sample) + + assert di_single_line_loopback_task.read() == sample + + +def test___digital_single_channel_writer___write_one_sample_multi_line___updates_output( + do_single_channel_multi_line_task: nidaqmx.Task, + di_multi_line_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalSingleChannelWriter(do_single_channel_multi_line_task.out_stream) + num_lines = _get_num_do_lines_in_task(do_single_channel_multi_line_task) + samples_to_write = 256 + + # "sweep" up to the final value, the only one we'll validate + for datum in _get_digital_data(num_lines, samples_to_write): + writer.write_one_sample_multi_line(_int_to_bool_array(num_lines, datum)) + + assert di_multi_line_loopback_task.read() == datum + + +def test___digital_single_channel_writer___write_one_sample_multi_line_with_wrong_dtype___raises_error_with_correct_dtype( + do_single_channel_multi_line_task: nidaqmx.Task, +) -> None: + writer = DigitalSingleChannelWriter(do_single_channel_multi_line_task.out_stream) + num_lines = _get_num_do_lines_in_task(do_single_channel_multi_line_task) + sample = numpy.full(num_lines, math.inf, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + writer.write_one_sample_multi_line(sample) + + assert "bool" in exc_info.value.args[0] + + +def test___digital_single_channel_writer___write_one_sample_port_byte___updates_output( + do_port1_task: nidaqmx.Task, + di_port1_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalSingleChannelWriter(do_port1_task.out_stream) + num_lines = _get_num_do_lines_in_task(do_port1_task) + samples_to_write = 256 + + # "sweep" up to the final value, the only one we'll validate + for datum in _get_digital_data(num_lines, samples_to_write): + writer.write_one_sample_port_byte(datum) + + assert di_port1_loopback_task.read() == datum + + +def test___digital_single_channel_writer___write_many_sample_port_byte___updates_output( + do_port1_task: nidaqmx.Task, + di_port1_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalSingleChannelWriter(do_port1_task.out_stream) + num_lines = _get_num_do_lines_in_task(do_port1_task) + samples_to_write = 256 + data = numpy.array(_get_digital_data(num_lines, samples_to_write), dtype=numpy.uint8) + + # "sweep" up to the final value, the only one we'll validate + writer.write_many_sample_port_byte(data) + + assert di_port1_loopback_task.read() == data[-1] + + +def test___digital_single_channel_writer___write_many_sample_port_byte_with_wrong_dtype___raises_error_with_correct_dtype( + do_port1_task: nidaqmx.Task, +) -> None: + writer = DigitalSingleChannelWriter(do_port1_task.out_stream) + samples_to_write = 256 + data = numpy.full(samples_to_write, math.inf, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + writer.write_many_sample_port_byte(data) + + assert "uint8" in exc_info.value.args[0] + + +def test___digital_single_channel_writer___write_one_sample_port_uint16___updates_output( + do_port1_task: nidaqmx.Task, + di_port1_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalSingleChannelWriter(do_port1_task.out_stream) + num_lines = _get_num_do_lines_in_task(do_port1_task) + samples_to_write = 256 + + # "sweep" up to the final value, the only one we'll validate + for datum in _get_digital_data(num_lines, samples_to_write): + writer.write_one_sample_port_uint16(datum) + + assert di_port1_loopback_task.read() == datum + + +def test___digital_single_channel_writer___write_many_sample_port_uint16___updates_output( + do_port1_task: nidaqmx.Task, + di_port1_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalSingleChannelWriter(do_port1_task.out_stream) + num_lines = _get_num_do_lines_in_task(do_port1_task) + samples_to_write = 256 + data = numpy.array(_get_digital_data(num_lines, samples_to_write), dtype=numpy.uint16) + + # "sweep" up to the final value, the only one we'll validate + writer.write_many_sample_port_uint16(data) + + assert di_port1_loopback_task.read() == data[-1] + + +def test___digital_single_channel_writer___write_many_sample_port_uint16_with_wrong_dtype___raises_error_with_correct_dtype( + do_port1_task: nidaqmx.Task, +) -> None: + writer = DigitalSingleChannelWriter(do_port1_task.out_stream) + samples_to_write = 256 + data = numpy.full(samples_to_write, math.inf, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + writer.write_many_sample_port_uint16(data) + + assert "uint16" in exc_info.value.args[0] + + +def test___digital_single_channel_writer___write_one_sample_port_uint32___updates_output( + do_port0_task: nidaqmx.Task, + di_port0_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalSingleChannelWriter(do_port0_task.out_stream) + num_lines = _get_num_do_lines_in_task(do_port0_task) + samples_to_write = 256 + + # "sweep" up to the final value, the only one we'll validate + for datum in _get_digital_data(num_lines, samples_to_write): + writer.write_one_sample_port_uint32(datum) + + assert di_port0_loopback_task.read() == datum + + +def test___digital_single_channel_writer___write_many_sample_port_uint32___updates_output( + do_port0_task: nidaqmx.Task, + di_port0_loopback_task: nidaqmx.Task, +) -> None: + writer = DigitalSingleChannelWriter(do_port0_task.out_stream) + num_lines = _get_num_do_lines_in_task(do_port0_task) + samples_to_write = 256 + data = numpy.array(_get_digital_data(num_lines, samples_to_write), dtype=numpy.uint32) + + # "sweep" up to the final value, the only one we'll validate + writer.write_many_sample_port_uint32(data) + + assert di_port0_loopback_task.read() == data[-1] + + +def test___digital_single_channel_writer___write_many_sample_port_uint32_with_wrong_dtype___raises_error_with_correct_dtype( + do_port0_task: nidaqmx.Task, +) -> None: + writer = DigitalSingleChannelWriter(do_port0_task.out_stream) + samples_to_write = 256 + data = numpy.full(samples_to_write, math.inf, dtype=numpy.float64) + + with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: + writer.write_many_sample_port_uint32(data) + + assert "uint32" in exc_info.value.args[0] diff --git a/tests/component/test_stream_writers_ao.py b/tests/component/test_stream_writers_ao.py deleted file mode 100644 index bef2e3e49..000000000 --- a/tests/component/test_stream_writers_ao.py +++ /dev/null @@ -1,400 +0,0 @@ -from __future__ import annotations - -import ctypes -from typing import Callable - -import numpy -import pytest - -import nidaqmx -import nidaqmx.system -from nidaqmx.stream_writers import ( - AnalogMultiChannelWriter, - AnalogSingleChannelWriter, - AnalogUnscaledWriter, -) - - -def _get_expected_voltage_for_chan(chan_index: int) -> float: - return float(chan_index + 1) - - -def _volts_to_codes(volts: float, max_code: int = 32767, max_voltage: float = 10.1) -> int: - return int(volts * max_code / max_voltage) - - -VOLTAGE_EPSILON = 1e-2 -# NOTE: You can't scale from volts to codes correctly without knowing the internal calibration -# constants. The internal reference has a healthy amount of overrange to ensure we can calibrate to -# device specifications. I've used 10.1 volts above to approximate that, but 100mv of accuracy is -# also fine since the expected output of each channel value will be 1 volt apart. -VOLTAGE_EPSILON_FOR_RAW = 1e-1 - - -@pytest.fixture -def ao_single_channel_task( - generate_task: Callable[[], nidaqmx.Task], - real_x_series_multiplexed_device: nidaqmx.system.Device, -) -> nidaqmx.Task: - task = generate_task() - chan_index = 0 - offset = _get_expected_voltage_for_chan(chan_index) - chan = task.ao_channels.add_ao_voltage_chan( - real_x_series_multiplexed_device.ao_physical_chans[chan_index].name, - min_val=0.0, - max_val=offset + VOLTAGE_EPSILON, - ) - # forcing the maximum range for binary read scaling to be predictable - chan.ao_dac_rng_high = 10 - chan.ao_dac_rng_low = -10 - - # we'll be doing simple on-demand, so start the task now - task.start() - - # set the output to a known initial value - task.write(0.0) - - return task - - -@pytest.fixture -def ai_single_channel_loopback_task( - generate_task: Callable[[], nidaqmx.Task], - real_x_series_multiplexed_device: nidaqmx.system.Device, -) -> nidaqmx.Task: - task = generate_task() - chan_index = 0 - task.ai_channels.add_ai_voltage_chan( - f"{real_x_series_multiplexed_device.name}/_ao{chan_index}_vs_aognd", - min_val=-10, - max_val=10, - ) - - # we'll be doing simple on-demand, so start the task now - task.start() - - return task - - -@pytest.fixture -def ao_multi_channel_task( - generate_task: Callable[[], nidaqmx.Task], - real_x_series_multiplexed_device: nidaqmx.system.Device, -) -> nidaqmx.Task: - task = generate_task() - num_chans = 2 - for chan_index in range(num_chans): - offset = _get_expected_voltage_for_chan(chan_index) - chan = task.ao_channels.add_ao_voltage_chan( - real_x_series_multiplexed_device.ao_physical_chans[chan_index].name, - min_val=0.0, - max_val=offset + VOLTAGE_EPSILON, - ) - # forcing the maximum range for binary read scaling to be predictable - chan.ao_dac_rng_high = 10 - chan.ao_dac_rng_low = -10 - - # we'll be doing simple on-demand, so start the task now - task.start() - - # set the output to a known initial value - task.write([0.0] * num_chans) - - return task - - -@pytest.fixture -def ai_multi_channel_loopback_task( - generate_task: Callable[[], nidaqmx.Task], - real_x_series_multiplexed_device: nidaqmx.system.Device, -) -> nidaqmx.Task: - task = generate_task() - num_chans = 2 - for chan_index in range(num_chans): - task.ai_channels.add_ai_voltage_chan( - f"{real_x_series_multiplexed_device.name}/_ao{chan_index}_vs_aognd", - min_val=-10, - max_val=10, - ) - - # we'll be doing simple on-demand, so start the task now - task.start() - - return task - - -def test___analog_single_channel_writer___write_one_sample___updates_output( - ao_single_channel_task: nidaqmx.Task, - ai_single_channel_loopback_task: nidaqmx.Task, -) -> None: - writer = AnalogSingleChannelWriter(ao_single_channel_task.out_stream) - expected = _get_expected_voltage_for_chan(0) - - writer.write_one_sample(expected) - - assert ai_single_channel_loopback_task.read() == pytest.approx(expected, abs=VOLTAGE_EPSILON) - - -def test___analog_single_channel_writer___write_many_sample___updates_output( - ao_single_channel_task: nidaqmx.Task, - ai_single_channel_loopback_task: nidaqmx.Task, -) -> None: - writer = AnalogSingleChannelWriter(ao_single_channel_task.out_stream) - samples_to_write = 10 - expected = _get_expected_voltage_for_chan(0) - # sweep up to the expected value, the only one we'll validate - data = numpy.linspace(0.0, expected, num=samples_to_write, dtype=numpy.float64) - - samples_written = writer.write_many_sample(data) - - assert samples_written == samples_to_write - assert ai_single_channel_loopback_task.read() == pytest.approx(expected, abs=VOLTAGE_EPSILON) - - -def test___analog_single_channel_writer___write_many_sample_with_wrong_dtype___raises_error_with_correct_dtype( - ao_single_channel_task: nidaqmx.Task, -) -> None: - writer = AnalogSingleChannelWriter(ao_single_channel_task.out_stream) - samples_to_write = 10 - expected = _get_expected_voltage_for_chan(0) - data = numpy.full(samples_to_write, expected, dtype=numpy.float32) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - _ = writer.write_many_sample(data) - - assert "float64" in exc_info.value.args[0] - - -def test___analog_multi_channel_writer___write_one_sample___updates_output( - ao_multi_channel_task: nidaqmx.Task, - ai_multi_channel_loopback_task: nidaqmx.Task, -) -> None: - writer = AnalogMultiChannelWriter(ao_multi_channel_task.out_stream) - num_channels = ao_multi_channel_task.number_of_channels - expected = [_get_expected_voltage_for_chan(chan_index) for chan_index in range(num_channels)] - data = numpy.asarray(expected, dtype=numpy.float64) - - writer.write_one_sample(data) - - assert ai_multi_channel_loopback_task.read() == pytest.approx(expected, abs=VOLTAGE_EPSILON) - - -def test___analog_multi_channel_writer___write_one_sample_with_wrong_dtype___raises_error_with_correct_dtype( - ao_multi_channel_task: nidaqmx.Task, -) -> None: - writer = AnalogMultiChannelWriter(ao_multi_channel_task.out_stream) - num_channels = ao_multi_channel_task.number_of_channels - data = numpy.full(num_channels, 0.0, dtype=numpy.float32) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - writer.write_one_sample(data) - - assert "float64" in exc_info.value.args[0] - - -def test___analog_multi_channel_writer___write_many_sample___updates_output( - ao_multi_channel_task: nidaqmx.Task, - ai_multi_channel_loopback_task: nidaqmx.Task, -) -> None: - writer = AnalogMultiChannelWriter(ao_multi_channel_task.out_stream) - num_channels = ao_multi_channel_task.number_of_channels - samples_to_write = 10 - expected = [_get_expected_voltage_for_chan(chan_index) for chan_index in range(num_channels)] - # sweep up to the expected values, the only one we'll validate - data = numpy.ascontiguousarray( - numpy.transpose( - numpy.linspace( - [0.0] * num_channels, - expected, - num=samples_to_write, - dtype=numpy.float64, - ) - ) - ) - - samples_written = writer.write_many_sample(data) - - assert samples_written == samples_to_write - assert ai_multi_channel_loopback_task.read() == pytest.approx(expected, abs=VOLTAGE_EPSILON) - - -def test___analog_multi_channel_writer___write_many_sample_with_wrong_dtype___raises_error_with_correct_dtype( - ao_multi_channel_task: nidaqmx.Task, -) -> None: - writer = AnalogMultiChannelWriter(ao_multi_channel_task.out_stream) - num_channels = ao_multi_channel_task.number_of_channels - samples_to_write = 10 - data = numpy.full((num_channels, samples_to_write), 0.0, dtype=numpy.float32) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - _ = writer.write_many_sample(data) - - assert "float64" in exc_info.value.args[0] - - -def test___analog_unscaled_writer___write_int16___updates_output( - ao_multi_channel_task: nidaqmx.Task, - ai_multi_channel_loopback_task: nidaqmx.Task, -) -> None: - writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) - num_channels = ao_multi_channel_task.number_of_channels - samples_to_write = 10 - expected = [_get_expected_voltage_for_chan(chan_index) for chan_index in range(num_channels)] - # sweep up to the expected values, the only one we'll validate - data = numpy.ascontiguousarray( - numpy.transpose( - numpy.linspace( - [0] * num_channels, - [_volts_to_codes(v) for v in expected], - num=samples_to_write, - dtype=numpy.int16, - ) - ) - ) - - samples_written = writer.write_int16(data) - - assert samples_written == samples_to_write - assert ai_multi_channel_loopback_task.read() == pytest.approx( - expected, abs=VOLTAGE_EPSILON_FOR_RAW - ) - - -def test___analog_unscaled_writer___write_int16_with_wrong_dtype___raises_error_with_correct_dtype( - ao_multi_channel_task: nidaqmx.Task, -) -> None: - writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) - num_channels = ao_multi_channel_task.number_of_channels - samples_to_write = 10 - data = numpy.full((num_channels, samples_to_write), 0.0, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - _ = writer.write_int16(data) - - assert "int16" in exc_info.value.args[0] - - -def test___analog_unscaled_writer___write_int32___updates_output( - ao_multi_channel_task: nidaqmx.Task, - ai_multi_channel_loopback_task: nidaqmx.Task, -) -> None: - writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) - num_channels = ao_multi_channel_task.number_of_channels - samples_to_write = 10 - expected = [_get_expected_voltage_for_chan(chan_index) for chan_index in range(num_channels)] - # sweep up to the expected values, the only one we'll validate - data = numpy.ascontiguousarray( - numpy.transpose( - numpy.linspace( - [0] * num_channels, - [_volts_to_codes(v) for v in expected], - num=samples_to_write, - dtype=numpy.int32, - ) - ) - ) - - samples_written = writer.write_int32(data) - - assert samples_written == samples_to_write - assert ai_multi_channel_loopback_task.read() == pytest.approx( - expected, abs=VOLTAGE_EPSILON_FOR_RAW - ) - - -def test___analog_unscaled_writer___write_int32_with_wrong_dtype___raises_error_with_correct_dtype( - ao_multi_channel_task: nidaqmx.Task, -) -> None: - writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) - num_channels = ao_multi_channel_task.number_of_channels - samples_to_write = 10 - data = numpy.full((num_channels, samples_to_write), 0.0, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - _ = writer.write_int32(data) - - assert "int32" in exc_info.value.args[0] - - -def test___analog_unscaled_writer___write_uint16___updates_output( - ao_multi_channel_task: nidaqmx.Task, - ai_multi_channel_loopback_task: nidaqmx.Task, -) -> None: - writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) - num_channels = ao_multi_channel_task.number_of_channels - samples_to_write = 10 - expected = [_get_expected_voltage_for_chan(chan_index) for chan_index in range(num_channels)] - # sweep up to the expected values, the only one we'll validate - data = numpy.ascontiguousarray( - numpy.transpose( - numpy.linspace( - [0] * num_channels, - [_volts_to_codes(v) for v in expected], - num=samples_to_write, - dtype=numpy.uint16, - ) - ) - ) - - samples_written = writer.write_uint16(data) - - assert samples_written == samples_to_write - assert ai_multi_channel_loopback_task.read() == pytest.approx( - expected, abs=VOLTAGE_EPSILON_FOR_RAW - ) - - -def test___analog_unscaled_writer___write_uint16_with_wrong_dtype___raises_error_with_correct_dtype( - ao_multi_channel_task: nidaqmx.Task, -) -> None: - writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) - num_channels = ao_multi_channel_task.number_of_channels - samples_to_write = 10 - data = numpy.full((num_channels, samples_to_write), 0.0, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - _ = writer.write_uint16(data) - - assert "uint16" in exc_info.value.args[0] - - -def test___analog_unscaled_writer___write_uint32___updates_output( - ao_multi_channel_task: nidaqmx.Task, - ai_multi_channel_loopback_task: nidaqmx.Task, -) -> None: - writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) - num_channels = ao_multi_channel_task.number_of_channels - samples_to_write = 10 - expected = [_get_expected_voltage_for_chan(chan_index) for chan_index in range(num_channels)] - # sweep up to the expected values, the only one we'll validate - data = numpy.ascontiguousarray( - numpy.transpose( - numpy.linspace( - [0] * num_channels, - [_volts_to_codes(v) for v in expected], - num=samples_to_write, - dtype=numpy.uint32, - ) - ) - ) - - samples_written = writer.write_uint32(data) - - assert samples_written == samples_to_write - assert ai_multi_channel_loopback_task.read() == pytest.approx( - expected, abs=VOLTAGE_EPSILON_FOR_RAW - ) - - -def test___analog_unscaled_writer___write_uint32_with_wrong_dtype___raises_error_with_correct_dtype( - ao_multi_channel_task: nidaqmx.Task, -) -> None: - writer = AnalogUnscaledWriter(ao_multi_channel_task.out_stream) - num_channels = ao_multi_channel_task.number_of_channels - samples_to_write = 10 - data = numpy.full((num_channels, samples_to_write), 0.0, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - _ = writer.write_uint32(data) - - assert "uint32" in exc_info.value.args[0] diff --git a/tests/component/test_stream_writers_do.py b/tests/component/test_stream_writers_do.py deleted file mode 100644 index c68740439..000000000 --- a/tests/component/test_stream_writers_do.py +++ /dev/null @@ -1,697 +0,0 @@ -from __future__ import annotations - -import ctypes -import math -from typing import Callable - -import numpy -import pytest - -import nidaqmx -import nidaqmx.system -from nidaqmx.constants import LineGrouping -from nidaqmx.stream_writers import DigitalMultiChannelWriter, DigitalSingleChannelWriter -from nidaqmx.utils import flatten_channel_string - - -def _start_do_task(task: nidaqmx.Task, is_port: bool = False, num_chans: int = 1) -> None: - # We'll be doing on-demand, so start the task and drive all lines low - task.start() - if is_port: - if num_chans == 8: - task.write(0) - else: - task.write([0] * num_chans) - else: - if num_chans == 1: - task.write(False) - else: - task.write([False] * num_chans) - - -@pytest.fixture -def do_single_line_task( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - task.do_channels.add_do_chan( - real_x_series_device.do_lines[0].name, line_grouping=LineGrouping.CHAN_FOR_ALL_LINES - ) - _start_do_task(task) - return task - - -@pytest.fixture -def do_single_channel_multi_line_task( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - chan = task.do_channels.add_do_chan( - flatten_channel_string(real_x_series_device.do_lines.channel_names[:8]), - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - _start_do_task(task, num_chans=chan.do_num_lines) - return task - - -@pytest.fixture -def do_multi_channel_multi_line_task( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - task.do_channels.add_do_chan( - flatten_channel_string(real_x_series_device.do_lines.channel_names[:8]), - line_grouping=LineGrouping.CHAN_PER_LINE, - ) - _start_do_task(task, num_chans=task.number_of_channels) - return task - - -@pytest.fixture -def do_port0_task( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - # X Series port 0 has either 32 or 8 lines. The former can only be used with 32-bit writes. The - # latter can be used with any sized port write. - task.do_channels.add_do_chan( - real_x_series_device.do_ports[0].name, - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - _start_do_task(task, is_port=True) - return task - - -@pytest.fixture -def do_port1_task( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - # X Series port 1 has 8 lines, and can be used with any sized port write. - task.do_channels.add_do_chan( - real_x_series_device.do_ports[1].name, - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - _start_do_task(task, is_port=True) - return task - - -@pytest.fixture -def do_multi_channel_port_task( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - # X Series port 1 has 8 lines, and can be used with any sized port write - task.do_channels.add_do_chan( - real_x_series_device.do_ports[1].name, - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - # X Series port 2 has 8 lines, and can be used with any sized port write - task.do_channels.add_do_chan( - real_x_series_device.do_ports[2].name, - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - _start_do_task(task, is_port=True, num_chans=task.number_of_channels) - return task - - -def _start_di_task(task: nidaqmx.Task) -> None: - # Don't reserve the lines, so we can read what DO is writing. - task.di_channels.all.di_tristate = False - task.start() - - -@pytest.fixture -def di_single_line_loopback_task( - generate_task: Callable[[], nidaqmx.Task], - real_x_series_device: nidaqmx.system.Device, -) -> nidaqmx.Task: - task = generate_task() - task.di_channels.add_di_chan( - real_x_series_device.di_lines[0].name, line_grouping=LineGrouping.CHAN_FOR_ALL_LINES - ) - _start_di_task(task) - return task - - -@pytest.fixture -def di_multi_line_loopback_task( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - task.di_channels.add_di_chan( - flatten_channel_string(real_x_series_device.di_lines.channel_names[:8]), - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - _start_di_task(task) - return task - - -@pytest.fixture -def di_port0_loopback_task( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - task.di_channels.add_di_chan( - real_x_series_device.di_ports[0].name, - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - _start_di_task(task) - return task - - -@pytest.fixture -def di_port0_loopback_task_32dio( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device_32dio: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - task.di_channels.add_di_chan( - real_x_series_device_32dio.di_ports[0].name, - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - _start_di_task(task) - return task - - -@pytest.fixture -def di_port1_loopback_task( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - task.di_channels.add_di_chan( - real_x_series_device.di_ports[1].name, - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - _start_di_task(task) - return task - - -@pytest.fixture -def di_port1_loopback_task_32dio( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device_32dio: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - task.di_channels.add_di_chan( - real_x_series_device_32dio.di_ports[1].name, - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - _start_di_task(task) - return task - - -@pytest.fixture -def di_port2_loopback_task( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - task.di_channels.add_di_chan( - real_x_series_device.di_ports[2].name, - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - _start_di_task(task) - return task - - -@pytest.fixture -def di_port2_loopback_task_32dio( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device_32dio: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - task.di_channels.add_di_chan( - real_x_series_device_32dio.di_ports[2].name, - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - _start_di_task(task) - return task - - -@pytest.fixture -def di_multi_channel_port_loopback_task( - generate_task: Callable[[], nidaqmx.Task], real_x_series_device: nidaqmx.system.Device -) -> nidaqmx.Task: - task = generate_task() - task.di_channels.add_di_chan( - real_x_series_device.di_ports[1].name, - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - task.di_channels.add_di_chan( - real_x_series_device.di_ports[2].name, - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - _start_di_task(task) - return task - - -def _get_num_lines_in_task(task: nidaqmx.Task) -> int: - return sum([chan.do_num_lines for chan in task.channels]) - - -def _get_digital_data_for_sample(num_lines: int, sample_number: int) -> int: - result = 0 - # "Count" from 0 in binary within each group of 8 lines, like simulated data. - for _ in range((num_lines + 7) // 8): - result = (result << 8) | sample_number - - line_mask = (2**num_lines) - 1 - return result & line_mask - - -def _get_digital_data(num_lines: int, num_samples: int) -> list[int]: - return [ - _get_digital_data_for_sample(num_lines, sample_number) - for sample_number in range(num_samples) - ] - - -def _get_digital_port_data_for_sample(task: nidaqmx.Task, sample_number: int) -> list[int]: - return [ - _get_digital_data_for_sample(chan.do_num_lines, sample_number) for chan in task.channels - ] - - -def _get_digital_port_data_port_major(task: nidaqmx.Task, num_samples: int) -> list[list[int]]: - return [_get_digital_data(chan.do_num_lines, num_samples) for chan in task.channels] - - -def _get_digital_port_data_sample_major(task: nidaqmx.Task, num_samples: int) -> list[list[int]]: - result = _get_digital_port_data_port_major(task, num_samples) - return numpy.transpose(result).tolist() - - -def _int_to_bool_array(num_lines: int, input: int) -> numpy.typing.NDArray[numpy.bool_]: - result = numpy.full(num_lines, True, dtype=numpy.bool_) - for bit in range(num_lines): - result[bit] = (input & (1 << bit)) != 0 - return result - - -def test___digital_single_channel_writer___write_one_sample_one_line___updates_output( - do_single_line_task: nidaqmx.Task, - di_single_line_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalSingleChannelWriter(do_single_line_task.out_stream) - sample = True - - writer.write_one_sample_one_line(sample) - - assert di_single_line_loopback_task.read() == sample - - -def test___digital_single_channel_writer___write_one_sample_multi_line___updates_output( - do_single_channel_multi_line_task: nidaqmx.Task, - di_multi_line_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalSingleChannelWriter(do_single_channel_multi_line_task.out_stream) - num_lines = _get_num_lines_in_task(do_single_channel_multi_line_task) - samples_to_write = 256 - - # "sweep" up to the final value, the only one we'll validate - for datum in _get_digital_data(num_lines, samples_to_write): - writer.write_one_sample_multi_line(_int_to_bool_array(num_lines, datum)) - - assert di_multi_line_loopback_task.read() == datum - - -def test___digital_single_channel_writer___write_one_sample_multi_line_with_wrong_dtype___raises_error_with_correct_dtype( - do_single_channel_multi_line_task: nidaqmx.Task, -) -> None: - writer = DigitalSingleChannelWriter(do_single_channel_multi_line_task.out_stream) - num_lines = _get_num_lines_in_task(do_single_channel_multi_line_task) - sample = numpy.full(num_lines, math.inf, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - writer.write_one_sample_multi_line(sample) - - assert "bool" in exc_info.value.args[0] - - -def test___digital_single_channel_writer___write_one_sample_port_byte___updates_output( - do_port1_task: nidaqmx.Task, - di_port1_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalSingleChannelWriter(do_port1_task.out_stream) - num_lines = _get_num_lines_in_task(do_port1_task) - samples_to_write = 256 - - # "sweep" up to the final value, the only one we'll validate - for datum in _get_digital_data(num_lines, samples_to_write): - writer.write_one_sample_port_byte(datum) - - assert di_port1_loopback_task.read() == datum - - -def test___digital_single_channel_writer___write_many_sample_port_byte___updates_output( - do_port1_task: nidaqmx.Task, - di_port1_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalSingleChannelWriter(do_port1_task.out_stream) - num_lines = _get_num_lines_in_task(do_port1_task) - samples_to_write = 256 - data = numpy.array(_get_digital_data(num_lines, samples_to_write), dtype=numpy.uint8) - - # "sweep" up to the final value, the only one we'll validate - writer.write_many_sample_port_byte(data) - - assert di_port1_loopback_task.read() == data[-1] - - -def test___digital_single_channel_writer___write_many_sample_port_byte_with_wrong_dtype___raises_error_with_correct_dtype( - do_port1_task: nidaqmx.Task, -) -> None: - writer = DigitalSingleChannelWriter(do_port1_task.out_stream) - samples_to_write = 256 - data = numpy.full(samples_to_write, math.inf, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - writer.write_many_sample_port_byte(data) - - assert "uint8" in exc_info.value.args[0] - - -def test___digital_single_channel_writer___write_one_sample_port_uint16___updates_output( - do_port1_task: nidaqmx.Task, - di_port1_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalSingleChannelWriter(do_port1_task.out_stream) - num_lines = _get_num_lines_in_task(do_port1_task) - samples_to_write = 256 - - # "sweep" up to the final value, the only one we'll validate - for datum in _get_digital_data(num_lines, samples_to_write): - writer.write_one_sample_port_uint16(datum) - - assert di_port1_loopback_task.read() == datum - - -def test___digital_single_channel_writer___write_many_sample_port_uint16___updates_output( - do_port1_task: nidaqmx.Task, - di_port1_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalSingleChannelWriter(do_port1_task.out_stream) - num_lines = _get_num_lines_in_task(do_port1_task) - samples_to_write = 256 - data = numpy.array(_get_digital_data(num_lines, samples_to_write), dtype=numpy.uint16) - - # "sweep" up to the final value, the only one we'll validate - writer.write_many_sample_port_uint16(data) - - assert di_port1_loopback_task.read() == data[-1] - - -def test___digital_single_channel_writer___write_many_sample_port_uint16_with_wrong_dtype___raises_error_with_correct_dtype( - do_port1_task: nidaqmx.Task, -) -> None: - writer = DigitalSingleChannelWriter(do_port1_task.out_stream) - samples_to_write = 256 - data = numpy.full(samples_to_write, math.inf, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - writer.write_many_sample_port_uint16(data) - - assert "uint16" in exc_info.value.args[0] - - -def test___digital_single_channel_writer___write_one_sample_port_uint32___updates_output( - do_port0_task: nidaqmx.Task, - di_port0_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalSingleChannelWriter(do_port0_task.out_stream) - num_lines = _get_num_lines_in_task(do_port0_task) - samples_to_write = 256 - - # "sweep" up to the final value, the only one we'll validate - for datum in _get_digital_data(num_lines, samples_to_write): - writer.write_one_sample_port_uint32(datum) - - assert di_port0_loopback_task.read() == datum - - -def test___digital_single_channel_writer___write_many_sample_port_uint32___updates_output( - do_port0_task: nidaqmx.Task, - di_port0_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalSingleChannelWriter(do_port0_task.out_stream) - num_lines = _get_num_lines_in_task(do_port0_task) - samples_to_write = 256 - data = numpy.array(_get_digital_data(num_lines, samples_to_write), dtype=numpy.uint32) - - # "sweep" up to the final value, the only one we'll validate - writer.write_many_sample_port_uint32(data) - - assert di_port0_loopback_task.read() == data[-1] - - -def test___digital_single_channel_writer___write_many_sample_port_uint32_with_wrong_dtype___raises_error_with_correct_dtype( - do_port0_task: nidaqmx.Task, -) -> None: - writer = DigitalSingleChannelWriter(do_port0_task.out_stream) - samples_to_write = 256 - data = numpy.full(samples_to_write, math.inf, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - writer.write_many_sample_port_uint32(data) - - assert "uint32" in exc_info.value.args[0] - - -def test___digital_multi_channel_writer___write_one_sample_one_line___updates_output( - do_single_line_task: nidaqmx.Task, - di_single_line_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_single_line_task.out_stream) - num_lines = _get_num_lines_in_task(do_single_line_task) - samples_to_write = 256 - - # "sweep" up to the final value, the only one we'll validate - for datum in _get_digital_data(num_lines, samples_to_write): - writer.write_one_sample_one_line(_int_to_bool_array(num_lines, datum)) - - assert di_single_line_loopback_task.read() == datum - - -def test___digital_multi_channel_writer___write_one_sample_multi_line___updates_output( - do_multi_channel_multi_line_task: nidaqmx.Task, - di_multi_line_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_multi_line_task.out_stream) - num_channels = do_multi_channel_multi_line_task.number_of_channels - samples_to_write = 256 - - # "sweep" up to the final value, the only one we'll validate - for datum in _get_digital_data(num_channels, samples_to_write): - data_to_write = _int_to_bool_array(num_channels, datum).reshape((num_channels, 1)) - writer.write_one_sample_multi_line(data_to_write) - - assert di_multi_line_loopback_task.read() == datum - - -def test___digital_multi_channel_writer___write_one_sample_multi_line_jagged___updates_output( - di_port0_loopback_task_32dio: nidaqmx.Task, - di_port1_loopback_task_32dio: nidaqmx.Task, - di_port2_loopback_task_32dio: nidaqmx.Task, - generate_task: Callable[[], nidaqmx.Task], - real_x_series_device_32dio: nidaqmx.system.Device, -) -> None: - task = generate_task() - for port in real_x_series_device_32dio.do_ports: - task.do_channels.add_do_chan( - port.name, - line_grouping=LineGrouping.CHAN_FOR_ALL_LINES, - ) - _start_do_task(task, is_port=True, num_chans=task.number_of_channels) - writer = DigitalMultiChannelWriter(task.out_stream) - num_channels = task.number_of_channels - samples_to_write = 0xA5 - - # "sweep" up to the final value, the only one we'll validate - for datum in _get_digital_data(num_channels * 32, samples_to_write): - data_to_write = _int_to_bool_array(num_channels * 32, datum).reshape((num_channels, 32)) - writer.write_one_sample_multi_line(data_to_write) - - assert di_port0_loopback_task_32dio.read() == datum & 0xFFFFFFFF - assert di_port1_loopback_task_32dio.read() == (datum >> 32) & 0xFF - assert di_port2_loopback_task_32dio.read() == (datum >> 64) & 0xFF - - -def test___digital_multi_channel_writer___write_one_sample_multi_line_with_wrong_dtype___raises_error_with_correct_dtype( - do_multi_channel_multi_line_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_multi_line_task.out_stream) - num_channels = do_multi_channel_multi_line_task.number_of_channels - sample = numpy.full((num_channels, 1), math.inf, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - writer.write_one_sample_multi_line(sample) - - assert "bool" in exc_info.value.args[0] - - -def test___digital_multi_channel_writer___write_one_sample_port_byte___updates_output( - do_multi_channel_port_task: nidaqmx.Task, - di_multi_channel_port_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) - samples_to_write = 256 - - # "sweep" up to the final value, the only one we'll validate - for datum in _get_digital_port_data_sample_major(do_multi_channel_port_task, samples_to_write): - writer.write_one_sample_port_byte(numpy.array(datum, dtype=numpy.uint8)) - - assert di_multi_channel_port_loopback_task.read() == datum - - -def test___digital_multi_channel_writer___write_one_sample_port_byte_with_wrong_dtype___raises_error_with_correct_dtype( - do_multi_channel_port_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) - num_channels = do_multi_channel_port_task.number_of_channels - data = numpy.full(num_channels, math.inf, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - writer.write_one_sample_port_byte(data) - - assert "uint8" in exc_info.value.args[0] - - -def test___digital_multi_channel_writer___write_many_sample_port_byte___updates_output( - do_multi_channel_port_task: nidaqmx.Task, - di_multi_channel_port_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) - samples_to_write = 256 - - # "sweep" up to the final value, the only one we'll validate - data = _get_digital_port_data_port_major(do_multi_channel_port_task, samples_to_write) - writer.write_many_sample_port_byte(numpy.array(data, dtype=numpy.uint8)) - - assert di_multi_channel_port_loopback_task.read() == _get_digital_port_data_for_sample( - do_multi_channel_port_task, samples_to_write - 1 - ) - - -def test___digital_multi_channel_writer___write_many_sample_port_byte_with_wrong_dtype___raises_error_with_correct_dtype( - do_multi_channel_port_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) - num_channels = do_multi_channel_port_task.number_of_channels - samples_to_write = 256 - data = numpy.full((num_channels, samples_to_write), math.inf, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - writer.write_many_sample_port_byte(data) - - assert "uint8" in exc_info.value.args[0] - - -def test___digital_multi_channel_writer___write_one_sample_port_uint16___updates_output( - do_multi_channel_port_task: nidaqmx.Task, - di_multi_channel_port_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) - samples_to_write = 256 - - # "sweep" up to the final value, the only one we'll validate - for datum in _get_digital_port_data_sample_major(do_multi_channel_port_task, samples_to_write): - writer.write_one_sample_port_uint16(numpy.array(datum, dtype=numpy.uint16)) - - assert di_multi_channel_port_loopback_task.read() == datum - - -def test___digital_multi_channel_writer___write_one_sample_port_uint16_with_wrong_dtype___raises_error_with_correct_dtype( - do_multi_channel_port_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) - num_channels = do_multi_channel_port_task.number_of_channels - data = numpy.full(num_channels, math.inf, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - writer.write_one_sample_port_uint16(data) - - assert "uint16" in exc_info.value.args[0] - - -def test___digital_multi_channel_writer___write_many_sample_port_uint16___updates_output( - do_multi_channel_port_task: nidaqmx.Task, - di_multi_channel_port_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) - samples_to_write = 256 - - # "sweep" up to the final value, the only one we'll validate - data = _get_digital_port_data_port_major(do_multi_channel_port_task, samples_to_write) - writer.write_many_sample_port_uint16(numpy.array(data, dtype=numpy.uint16)) - - assert di_multi_channel_port_loopback_task.read() == _get_digital_port_data_for_sample( - do_multi_channel_port_task, samples_to_write - 1 - ) - - -def test___digital_multi_channel_writer___write_many_sample_port_uint16_with_wrong_dtype___raises_error_with_correct_dtype( - do_multi_channel_port_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) - num_channels = do_multi_channel_port_task.number_of_channels - samples_to_write = 256 - data = numpy.full((num_channels, samples_to_write), math.inf, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - writer.write_many_sample_port_uint16(data) - - assert "uint16" in exc_info.value.args[0] - - -def test___digital_multi_channel_writer___write_one_sample_port_uint32___updates_output( - do_multi_channel_port_task: nidaqmx.Task, - di_multi_channel_port_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) - samples_to_write = 256 - - # "sweep" up to the final value, the only one we'll validate - for datum in _get_digital_port_data_sample_major(do_multi_channel_port_task, samples_to_write): - writer.write_one_sample_port_uint32(numpy.array(datum, dtype=numpy.uint32)) - - assert di_multi_channel_port_loopback_task.read() == datum - - -def test___digital_multi_channel_writer___write_one_sample_port_uint32_with_wrong_dtype___raises_error_with_correct_dtype( - do_multi_channel_port_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) - num_channels = do_multi_channel_port_task.number_of_channels - data = numpy.full(num_channels, math.inf, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - writer.write_one_sample_port_uint32(data) - - assert "uint32" in exc_info.value.args[0] - - -def test___digital_multi_channel_writer___write_many_sample_port_uint32___updates_output( - do_multi_channel_port_task: nidaqmx.Task, - di_multi_channel_port_loopback_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) - samples_to_write = 256 - - # "sweep" up to the final value, the only one we'll validate - data = _get_digital_port_data_port_major(do_multi_channel_port_task, samples_to_write) - writer.write_many_sample_port_uint32(numpy.array(data, dtype=numpy.uint32)) - - assert di_multi_channel_port_loopback_task.read() == _get_digital_port_data_for_sample( - do_multi_channel_port_task, samples_to_write - 1 - ) - - -def test___digital_multi_channel_writer___write_many_sample_port_uint32_with_wrong_dtype___raises_error_with_correct_dtype( - do_multi_channel_port_task: nidaqmx.Task, -) -> None: - writer = DigitalMultiChannelWriter(do_multi_channel_port_task.out_stream) - num_channels = do_multi_channel_port_task.number_of_channels - samples_to_write = 256 - data = numpy.full((num_channels, samples_to_write), math.inf, dtype=numpy.float64) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - writer.write_many_sample_port_uint32(data) - - assert "uint32" in exc_info.value.args[0] From 4b39a0f47e95e6a5aa40c801198e3433e3c4a62c Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Wed, 10 Sep 2025 09:34:35 -0500 Subject: [PATCH 2/9] remove test_stream_writers.py --- tests/component/test_stream_writers.py | 77 -------------------------- 1 file changed, 77 deletions(-) delete mode 100644 tests/component/test_stream_writers.py diff --git a/tests/component/test_stream_writers.py b/tests/component/test_stream_writers.py deleted file mode 100644 index f7770cf85..000000000 --- a/tests/component/test_stream_writers.py +++ /dev/null @@ -1,77 +0,0 @@ -import ctypes - -import numpy -import pytest - -import nidaqmx -import nidaqmx.system -from nidaqmx.stream_writers import AnalogMultiChannelWriter, AnalogSingleChannelWriter - - -@pytest.fixture -def ao_single_channel_task( - task: nidaqmx.Task, sim_6363_device: nidaqmx.system.Device -) -> nidaqmx.Task: - task.ao_channels.add_ao_voltage_chan(sim_6363_device.ao_physical_chans[0].name) - return task - - -@pytest.fixture -def ao_multi_channel_task( - task: nidaqmx.Task, sim_6363_device: nidaqmx.system.Device -) -> nidaqmx.Task: - task.ao_channels.add_ao_voltage_chan(sim_6363_device.ao_physical_chans[0].name) - task.ao_channels.add_ao_voltage_chan(sim_6363_device.ao_physical_chans[1].name) - return task - - -def test___analog_single_channel_writer___write_many_sample___returns_samples_written( - ao_single_channel_task: nidaqmx.Task, -): - writer = AnalogSingleChannelWriter(ao_single_channel_task.in_stream, auto_start=True) - samples_to_write = 10 - data = numpy.full(samples_to_write, 1.234, dtype=numpy.float64) - - samples_written = writer.write_many_sample(data) - - assert samples_written == samples_to_write - - -def test___analog_single_channel_writer___write_many_sample_with_wrong_dtype___raises_error_with_correct_dtype( - ao_single_channel_task: nidaqmx.Task, -): - writer = AnalogSingleChannelWriter(ao_single_channel_task.in_stream, auto_start=True) - samples_to_write = 10 - data = numpy.full(samples_to_write, 1.234, dtype=numpy.float32) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - _ = writer.write_many_sample(data) - - assert "float64" in exc_info.value.args[0] - - -def test___analog_multi_channel_writer___write_many_sample___returns_samples_written( - ao_multi_channel_task: nidaqmx.Task, -): - writer = AnalogMultiChannelWriter(ao_multi_channel_task.in_stream, auto_start=True) - num_channels = ao_multi_channel_task.number_of_channels - samples_to_write = 10 - data = numpy.full((num_channels, samples_to_write), 1.234, dtype=numpy.float64) - - samples_written = writer.write_many_sample(data) - - assert samples_written == samples_to_write - - -def test___analog_multi_channel_writer___write_many_sample_with_wrong_dtype___raises_error_with_correct_dtype( - ao_multi_channel_task: nidaqmx.Task, -): - writer = AnalogMultiChannelWriter(ao_multi_channel_task.in_stream, auto_start=True) - num_channels = ao_multi_channel_task.number_of_channels - samples_to_write = 10 - data = numpy.full((num_channels, samples_to_write), 1.234, dtype=numpy.float32) - - with pytest.raises((ctypes.ArgumentError, TypeError)) as exc_info: - _ = writer.write_many_sample(data) - - assert "float64" in exc_info.value.args[0] From 96d5e6e34f2712b17c451e0cc4bfdb27164586b6 Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Wed, 10 Sep 2025 10:20:26 -0500 Subject: [PATCH 3/9] move utils out of conftest --- tests/component/_analog_utils.py | 74 ++++++ tests/component/_digital_utils.py | 127 +++++++++++ tests/component/_utils.py | 13 ++ tests/component/conftest.py | 213 ++---------------- .../test_analog_multi_channel_reader.py | 24 +- .../test_analog_single_channel_reader.py | 32 +-- .../test_analog_unscaled_reader.py | 2 +- .../test_digital_multi_channel_reader.py | 4 +- .../test_digital_single_channel_reader.py | 4 +- .../stream_readers/test_power_readers_ai.py | 2 +- .../test_analog_multi_channel_writer.py | 8 +- .../test_analog_single_channel_writer.py | 8 +- .../test_analog_unscaled_writer.py | 20 +- .../test_digital_multi_channel_writer.py | 2 +- .../test_digital_single_channel_writer.py | 2 +- tests/component/task/test_task_read_ai.py | 20 +- .../task/test_task_read_waveform_ai.py | 20 +- .../task/test_task_read_waveform_di.py | 2 +- 18 files changed, 301 insertions(+), 276 deletions(-) create mode 100644 tests/component/_analog_utils.py create mode 100644 tests/component/_digital_utils.py create mode 100644 tests/component/_utils.py diff --git a/tests/component/_analog_utils.py b/tests/component/_analog_utils.py new file mode 100644 index 000000000..62b7d8071 --- /dev/null +++ b/tests/component/_analog_utils.py @@ -0,0 +1,74 @@ +"""Shared utilities for analog component tests.""" + +from __future__ import annotations + +import pytest + + +# Simulated DAQ voltage data is a noisy sinewave within the range of the minimum and maximum values +# of the virtual channel. We can leverage this behavior to validate we get the correct data from +# the Python bindings. +def _get_voltage_offset_for_chan(chan_index: int) -> float: + return float(chan_index + 1) + + +def _get_voltage_setpoint_for_chan(chan_index: int) -> float: + return float(chan_index + 1) + + +def _get_current_setpoint_for_chan(chan_index: int) -> float: + return float(chan_index + 1) + + +def _get_expected_voltage_for_chan(chan_index: int) -> float: + return float(chan_index + 1) + + +def _volts_to_codes(volts: float, max_code: int = 32767, max_voltage: float = 10.0) -> int: + return int(volts * max_code / max_voltage) + + +def _pwr_volts_to_codes(volts: float, codes_per_volt: int = 4096) -> int: + return int(volts * codes_per_volt) + + +def _pwr_current_to_codes(current: float, codes_per_amp: int = 8192) -> int: + return int(current * codes_per_amp) + + +def _get_voltage_code_setpoint_for_chan(chan_index: int) -> int: + return _pwr_volts_to_codes(_get_voltage_setpoint_for_chan(chan_index)) + + +def _get_current_code_setpoint_for_chan(chan_index: int) -> int: + return _pwr_current_to_codes(_get_current_setpoint_for_chan(chan_index)) + + +# Note: Since we only use positive voltages, this works fine for both signed and unsigned reads. +def _get_voltage_code_offset_for_chan(chan_index: int) -> int: + voltage_limits = _get_voltage_offset_for_chan(chan_index) + return _volts_to_codes(voltage_limits) + + +def _assert_equal_2d(data: list[list[float]], expected: list[list[float]], abs: float) -> None: + assert len(data) == len(expected) + for i in range(len(data)): + assert data[i] == pytest.approx(expected[i], abs=abs) + +# NOTE: We use simulated signals for AI validation, so we can be fairly strict here. +AI_VOLTAGE_EPSILON = 1e-3 + +# NOTE: We must use real signals for AO validation, but we aren't validating hardware accuracy here. +# This should be wide enough tolerance to allow for uncalibrated boards while still ensuring we are +# correctly configuring hardware. +AO_VOLTAGE_EPSILON = 1e-2 + +# NOTE: You can't scale from volts to codes correctly without knowing the internal calibration +# constants. The internal reference has a healthy amount of overrange to ensure we can calibrate to +# device specifications. I've used 10.1 volts above to approximate that, but 100mv of accuracy is +# also fine since the expected output of each channel value will be 1 volt apart. +RAW_VOLTAGE_EPSILON = 1e-1 + +VOLTAGE_CODE_EPSILON = round(_volts_to_codes(AI_VOLTAGE_EPSILON)) +POWER_EPSILON = 1e-3 +POWER_BINARY_EPSILON = 1 diff --git a/tests/component/_digital_utils.py b/tests/component/_digital_utils.py new file mode 100644 index 000000000..ad5f6e5ec --- /dev/null +++ b/tests/component/_digital_utils.py @@ -0,0 +1,127 @@ +"""Shared utilities for digital component tests.""" + +from __future__ import annotations + +from typing import Callable, TypeVar + +import numpy +from nitypes.waveform import DigitalWaveform + +import nidaqmx + +_D = TypeVar("_D", bound=numpy.generic) + + +def _start_di_task(task: nidaqmx.Task) -> None: + # Don't reserve the lines, so we can read what DO is writing. + task.di_channels.all.di_tristate = False + task.start() + + +def _start_do_task(task: nidaqmx.Task, is_port: bool = False, num_chans: int = 1) -> None: + # We'll be doing on-demand, so start the task and drive all lines low + task.start() + if is_port: + if num_chans == 8: + task.write(0) + else: + task.write([0] * num_chans) + else: + if num_chans == 1: + task.write(False) + else: + task.write([False] * num_chans) + + +def _get_num_di_lines_in_task(task: nidaqmx.Task) -> int: + return sum([chan.di_num_lines for chan in task.channels]) + + +def _get_num_do_lines_in_task(task: nidaqmx.Task) -> int: + return sum([chan.do_num_lines for chan in task.channels]) + + +def _get_digital_data_for_sample(num_lines: int, sample_number: int) -> int: + result = 0 + # Simulated digital signals "count" from 0 in binary within each group of 8 lines. + for _ in range((num_lines + 7) // 8): + result = (result << 8) | sample_number + + line_mask = (2**num_lines) - 1 + return result & line_mask + + +def _get_expected_data_for_line(num_samples: int, line_number: int) -> list[int]: + data = [] + # Simulated digital signals "count" from 0 in binary within each group of 8 lines. + # Each line represents a bit in the binary representation of the sample number. + # - line 0 represents bit 0 (LSB) - alternates every sample: 0,1,0,1,0,1,0,1... + # - line 1 represents bit 1 - alternates every 2 samples: 0,0,1,1,0,0,1,1... + # - line 2 represents bit 2 - alternates every 4 samples: 0,0,0,0,1,1,1,1... + line_number %= 8 + for sample_num in range(num_samples): + bit_value = (sample_num >> line_number) & 1 + data.append(bit_value) + return data + + +def _get_digital_data(num_lines: int, num_samples: int) -> list[int]: + return [ + _get_digital_data_for_sample(num_lines, sample_number) + for sample_number in range(num_samples) + ] + + +def _get_expected_digital_port_data_port_major( + task: nidaqmx.Task, num_samples: int +) -> list[list[int]]: + return [_get_digital_data(chan.di_num_lines, num_samples) for chan in task.channels] + + +def _get_expected_digital_port_data_sample_major( + task: nidaqmx.Task, num_samples: int +) -> list[list[int]]: + result = _get_expected_digital_port_data_port_major(task, num_samples) + return numpy.transpose(result).tolist() + + +def _get_digital_port_data_for_sample(task: nidaqmx.Task, sample_number: int) -> list[int]: + return [ + _get_digital_data_for_sample(chan.do_num_lines, sample_number) for chan in task.channels + ] + + +def _get_digital_port_data_port_major(task: nidaqmx.Task, num_samples: int) -> list[list[int]]: + return [_get_digital_data(chan.do_num_lines, num_samples) for chan in task.channels] + + +def _get_digital_port_data_sample_major(task: nidaqmx.Task, num_samples: int) -> list[list[int]]: + result = _get_digital_port_data_port_major(task, num_samples) + return numpy.transpose(result).tolist() + + +def _bool_array_to_int(bool_array: numpy.typing.NDArray[numpy.bool_]) -> int: + result = 0 + # Simulated data is little-endian + for bit in bool_array[::-1]: + result = (result << 1) | int(bit) + return result + + +def _int_to_bool_array(num_lines: int, input: int) -> numpy.typing.NDArray[numpy.bool_]: + result = numpy.full(num_lines, True, dtype=numpy.bool_) + for bit in range(num_lines): + result[bit] = (input & (1 << bit)) != 0 + return result + + +def _get_waveform_data(waveform: DigitalWaveform) -> list[int]: + assert isinstance(waveform, DigitalWaveform) + return [_bool_array_to_int(sample) for sample in waveform.data] + + +def _read_and_copy( + read_func: Callable[[numpy.typing.NDArray[_D]], None], array: numpy.typing.NDArray[_D] +) -> numpy.typing.NDArray[_D]: + read_func(array) + return array.copy() diff --git a/tests/component/_utils.py b/tests/component/_utils.py new file mode 100644 index 000000000..4814b7e86 --- /dev/null +++ b/tests/component/_utils.py @@ -0,0 +1,13 @@ +"""Shared utilities for component tests.""" + +from __future__ import annotations + +from datetime import timezone + +from hightime import datetime as ht_datetime + + +def _is_timestamp_close_to_now(timestamp: ht_datetime, tolerance_seconds: float = 1.0) -> bool: + current_time = ht_datetime.now(timezone.utc) + time_diff = abs((timestamp - current_time).total_seconds()) + return time_diff <= tolerance_seconds diff --git a/tests/component/conftest.py b/tests/component/conftest.py index 6976709a0..55136b611 100644 --- a/tests/component/conftest.py +++ b/tests/component/conftest.py @@ -1,205 +1,24 @@ -"""Shared fixtures and utilities for component tests.""" +"""Shared fixtures for component tests.""" from __future__ import annotations -from datetime import timezone -from typing import Callable, TypeVar +from typing import Callable -import numpy import pytest -from hightime import datetime as ht_datetime -from nitypes.waveform import DigitalWaveform import nidaqmx import nidaqmx.system from nidaqmx.constants import AcquisitionType, LineGrouping from nidaqmx.utils import flatten_channel_string - - -def _start_do_task(task: nidaqmx.Task, is_port: bool = False, num_chans: int = 1) -> None: - # We'll be doing on-demand, so start the task and drive all lines low - task.start() - if is_port: - if num_chans == 8: - task.write(0) - else: - task.write([0] * num_chans) - else: - if num_chans == 1: - task.write(False) - else: - task.write([False] * num_chans) - - -def _start_di_task(task: nidaqmx.Task) -> None: - # Don't reserve the lines, so we can read what DO is writing. - task.di_channels.all.di_tristate = False - task.start() - - -# Simulated DAQ voltage data is a noisy sinewave within the range of the minimum and maximum values -# of the virtual channel. We can leverage this behavior to validate we get the correct data from -# the Python bindings. -def _get_voltage_offset_for_chan(chan_index: int) -> float: - return float(chan_index + 1) - - -def _get_voltage_setpoint_for_chan(chan_index: int) -> float: - return float(chan_index + 1) - - -def _get_current_setpoint_for_chan(chan_index: int) -> float: - return float(chan_index + 1) - - -def _get_expected_voltage_for_chan(chan_index: int) -> float: - return float(chan_index + 1) - - -def _volts_to_codes(volts: float, max_code: int = 32767, max_voltage: float = 10.0) -> int: - return int(volts * max_code / max_voltage) - - -def _pwr_volts_to_codes(volts: float, codes_per_volt: int = 4096) -> int: - return int(volts * codes_per_volt) - - -def _pwr_current_to_codes(current: float, codes_per_amp: int = 8192) -> int: - return int(current * codes_per_amp) - - -def _get_voltage_code_setpoint_for_chan(chan_index: int) -> int: - return _pwr_volts_to_codes(_get_voltage_setpoint_for_chan(chan_index)) - - -def _get_current_code_setpoint_for_chan(chan_index: int) -> int: - return _pwr_current_to_codes(_get_current_setpoint_for_chan(chan_index)) - - -# Note: Since we only use positive voltages, this works fine for both signed and unsigned reads. -def _get_voltage_code_offset_for_chan(chan_index: int) -> int: - voltage_limits = _get_voltage_offset_for_chan(chan_index) - return _volts_to_codes(voltage_limits) - - -def _get_num_di_lines_in_task(task: nidaqmx.Task) -> int: - return sum([chan.di_num_lines for chan in task.channels]) - - -def _get_num_do_lines_in_task(task: nidaqmx.Task) -> int: - return sum([chan.do_num_lines for chan in task.channels]) - - -def _get_digital_data_for_sample(num_lines: int, sample_number: int) -> int: - result = 0 - # Simulated digital signals "count" from 0 in binary within each group of 8 lines. - for _ in range((num_lines + 7) // 8): - result = (result << 8) | sample_number - - line_mask = (2**num_lines) - 1 - return result & line_mask - - -def _get_expected_data_for_line(num_samples: int, line_number: int) -> list[int]: - data = [] - # Simulated digital signals "count" from 0 in binary within each group of 8 lines. - # Each line represents a bit in the binary representation of the sample number. - # - line 0 represents bit 0 (LSB) - alternates every sample: 0,1,0,1,0,1,0,1... - # - line 1 represents bit 1 - alternates every 2 samples: 0,0,1,1,0,0,1,1... - # - line 2 represents bit 2 - alternates every 4 samples: 0,0,0,0,1,1,1,1... - line_number %= 8 - for sample_num in range(num_samples): - bit_value = (sample_num >> line_number) & 1 - data.append(bit_value) - return data - - -def _get_digital_data(num_lines: int, num_samples: int) -> list[int]: - return [ - _get_digital_data_for_sample(num_lines, sample_number) - for sample_number in range(num_samples) - ] - - -def _get_expected_digital_port_data_port_major( - task: nidaqmx.Task, num_samples: int -) -> list[list[int]]: - return [_get_digital_data(chan.di_num_lines, num_samples) for chan in task.channels] - - -def _get_expected_digital_port_data_sample_major( - task: nidaqmx.Task, num_samples: int -) -> list[list[int]]: - result = _get_expected_digital_port_data_port_major(task, num_samples) - return numpy.transpose(result).tolist() - - -def _get_digital_port_data_for_sample(task: nidaqmx.Task, sample_number: int) -> list[int]: - return [ - _get_digital_data_for_sample(chan.do_num_lines, sample_number) for chan in task.channels - ] - - -def _get_digital_port_data_port_major(task: nidaqmx.Task, num_samples: int) -> list[list[int]]: - return [_get_digital_data(chan.do_num_lines, num_samples) for chan in task.channels] - - -def _get_digital_port_data_sample_major(task: nidaqmx.Task, num_samples: int) -> list[list[int]]: - result = _get_digital_port_data_port_major(task, num_samples) - return numpy.transpose(result).tolist() - - -def _bool_array_to_int(bool_array: numpy.typing.NDArray[numpy.bool_]) -> int: - result = 0 - # Simulated data is little-endian - for bit in bool_array[::-1]: - result = (result << 1) | int(bit) - return result - - -def _int_to_bool_array(num_lines: int, input: int) -> numpy.typing.NDArray[numpy.bool_]: - result = numpy.full(num_lines, True, dtype=numpy.bool_) - for bit in range(num_lines): - result[bit] = (input & (1 << bit)) != 0 - return result - - -def _get_waveform_data(waveform: DigitalWaveform) -> list[int]: - assert isinstance(waveform, DigitalWaveform) - return [_bool_array_to_int(sample) for sample in waveform.data] - - -def _read_and_copy( - read_func: Callable[[numpy.typing.NDArray[_D]], None], array: numpy.typing.NDArray[_D] -) -> numpy.typing.NDArray[_D]: - read_func(array) - return array.copy() - - -def _is_timestamp_close_to_now(timestamp: ht_datetime, tolerance_seconds: float = 1.0) -> bool: - current_time = ht_datetime.now(timezone.utc) - time_diff = abs((timestamp - current_time).total_seconds()) - return time_diff <= tolerance_seconds - - -def _assert_equal_2d(data: list[list[float]], expected: list[list[float]], abs: float) -> None: - assert len(data) == len(expected) - for i in range(len(data)): - assert data[i] == pytest.approx(expected[i], abs=abs) - - -_D = TypeVar("_D", bound=numpy.generic) - -VOLTAGE_EPSILON = 1e-2 -VOLTAGE_CODE_EPSILON = round(_volts_to_codes(VOLTAGE_EPSILON)) -POWER_EPSILON = 1e-3 -POWER_BINARY_EPSILON = 1 - -# NOTE: You can't scale from volts to codes correctly without knowing the internal calibration -# constants. The internal reference has a healthy amount of overrange to ensure we can calibrate to -# device specifications. I've used 10.1 volts above to approximate that, but 100mv of accuracy is -# also fine since the expected output of each channel value will be 1 volt apart. -VOLTAGE_EPSILON_FOR_RAW = 1e-1 +from tests.component._analog_utils import ( + AI_VOLTAGE_EPSILON, + AO_VOLTAGE_EPSILON, + _get_current_setpoint_for_chan, + _get_expected_voltage_for_chan, + _get_voltage_offset_for_chan, + _get_voltage_setpoint_for_chan, +) +from tests.component._digital_utils import _start_di_task, _start_do_task @pytest.fixture @@ -211,7 +30,7 @@ def ai_single_channel_task( task.ai_channels.add_ai_voltage_chan( sim_6363_device.ai_physical_chans[0].name, min_val=offset, - max_val=offset + VOLTAGE_EPSILON, + max_val=offset + AI_VOLTAGE_EPSILON, ) return task @@ -236,7 +55,7 @@ def ai_single_channel_task_with_high_rate( task.ai_channels.add_ai_voltage_chan( sim_charge_device.ai_physical_chans[0].name, min_val=offset, - max_val=offset + VOLTAGE_EPSILON, + max_val=offset + AI_VOLTAGE_EPSILON, ) task.timing.cfg_samp_clk_timing( rate=10_000_000, sample_mode=AcquisitionType.FINITE, samps_per_chan=50 @@ -255,7 +74,7 @@ def ai_multi_channel_task( sim_6363_device.ai_physical_chans[chan_index].name, min_val=offset, # min and max must be different, so add a small epsilon - max_val=offset + VOLTAGE_EPSILON, + max_val=offset + AI_VOLTAGE_EPSILON, ) # forcing the maximum range for binary read scaling to be predictable chan.ai_rng_high = 10 @@ -596,7 +415,7 @@ def ao_single_channel_task( chan = task.ao_channels.add_ao_voltage_chan( real_x_series_multiplexed_device.ao_physical_chans[chan_index].name, min_val=0.0, - max_val=offset + VOLTAGE_EPSILON, + max_val=offset + AO_VOLTAGE_EPSILON, ) # forcing the maximum range for binary read scaling to be predictable chan.ao_dac_rng_high = 10 @@ -644,7 +463,7 @@ def ao_multi_channel_task( chan = task.ao_channels.add_ao_voltage_chan( real_x_series_multiplexed_device.ao_physical_chans[chan_index].name, min_val=0.0, - max_val=offset + VOLTAGE_EPSILON, + max_val=offset + AO_VOLTAGE_EPSILON, ) # forcing the maximum range for binary read scaling to be predictable chan.ao_dac_rng_high = 10 diff --git a/tests/component/stream_readers/test_analog_multi_channel_reader.py b/tests/component/stream_readers/test_analog_multi_channel_reader.py index 727888a61..c26eb2075 100644 --- a/tests/component/stream_readers/test_analog_multi_channel_reader.py +++ b/tests/component/stream_readers/test_analog_multi_channel_reader.py @@ -13,11 +13,11 @@ from nidaqmx.constants import WaveformAttributeMode from nidaqmx.error_codes import DAQmxErrors from nidaqmx.stream_readers import AnalogMultiChannelReader, DaqError -from tests.component.conftest import ( - VOLTAGE_EPSILON, +from tests.component._analog_utils import ( + AI_VOLTAGE_EPSILON, _get_voltage_offset_for_chan, - _is_timestamp_close_to_now, ) +from tests.component._utils import _is_timestamp_close_to_now def test___analog_multi_channel_reader___read_one_sample___returns_valid_samples( @@ -30,7 +30,7 @@ def test___analog_multi_channel_reader___read_one_sample___returns_valid_samples reader.read_one_sample(data) expected = [_get_voltage_offset_for_chan(chan_index) for chan_index in range(num_channels)] - assert data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) def test___analog_multi_channel_reader___read_one_sample_with_wrong_dtype___raises_error_with_correct_dtype( @@ -58,7 +58,7 @@ def test___analog_multi_channel_reader___read_many_sample___returns_valid_sample assert samples_read == samples_to_read expected_vals = [_get_voltage_offset_for_chan(chan_index) for chan_index in range(num_channels)] - assert data == pytest.approx(expected_vals, abs=VOLTAGE_EPSILON) + assert data == pytest.approx(expected_vals, abs=AI_VOLTAGE_EPSILON) def test___analog_multi_channel_reader___read_many_sample_with_wrong_dtype___raises_error_with_correct_dtype( @@ -110,7 +110,7 @@ def test___analog_multi_channel_reader___read_waveforms___returns_valid_waveform for chan_index, waveform in enumerate(waveforms): assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(chan_index) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert isinstance(waveform.timing.timestamp, ht_datetime) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) @@ -138,7 +138,7 @@ def test___analog_multi_channel_reader___read_waveforms_no_args___returns_valid_ for chan_index, waveform in enumerate(waveforms): assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(chan_index) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert isinstance(waveform.timing.timestamp, ht_datetime) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) @@ -171,7 +171,7 @@ def test___analog_multi_channel_reader___read_waveforms_in_place___populates_val for chan_index, waveform in enumerate(waveforms): assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(chan_index) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert isinstance(waveform.timing.timestamp, ht_datetime) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) @@ -239,7 +239,7 @@ def test___analog_multi_channel_reader_with_timing_flag___read_waveforms___only_ for chan_index, waveform in enumerate(waveforms): assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(chan_index) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert isinstance(waveform.timing.timestamp, ht_datetime) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval_mode == SampleIntervalMode.REGULAR @@ -269,7 +269,7 @@ def test___analog_multi_channel_reader_with_extended_properties_flag___read_wave for chan_index, waveform in enumerate(waveforms): assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(chan_index) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert waveform.timing.sample_interval_mode == SampleIntervalMode.NONE assert ( waveform.channel_name == ai_multi_channel_task_with_timing.ai_channels[chan_index].name @@ -300,7 +300,7 @@ def test___analog_multi_channel_reader_with_both_flags___read_waveforms___includ for chan_index, waveform in enumerate(waveforms): assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(chan_index) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert isinstance(waveform.timing.timestamp, ht_datetime) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval_mode == SampleIntervalMode.REGULAR @@ -332,7 +332,7 @@ def test___analog_multi_channel_reader_with_none_flag___read_waveforms___minimal for chan_index, waveform in enumerate(waveforms): assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(chan_index) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert waveform.timing.sample_interval_mode == SampleIntervalMode.NONE assert waveform.channel_name == "" assert waveform.units == "" diff --git a/tests/component/stream_readers/test_analog_single_channel_reader.py b/tests/component/stream_readers/test_analog_single_channel_reader.py index 04c2c38a1..46c0c3120 100644 --- a/tests/component/stream_readers/test_analog_single_channel_reader.py +++ b/tests/component/stream_readers/test_analog_single_channel_reader.py @@ -15,11 +15,11 @@ from nidaqmx.constants import AcquisitionType, WaveformAttributeMode from nidaqmx.error_codes import DAQmxErrors from nidaqmx.stream_readers import AnalogSingleChannelReader, DaqError -from tests.component.conftest import ( - VOLTAGE_EPSILON, +from tests.component._analog_utils import ( + AI_VOLTAGE_EPSILON, _get_voltage_offset_for_chan, - _is_timestamp_close_to_now, ) +from tests.component._utils import _is_timestamp_close_to_now def test___analog_single_channel_reader___read_one_sample___returns_valid_samples( @@ -30,7 +30,7 @@ def test___analog_single_channel_reader___read_one_sample___returns_valid_sample data = reader.read_one_sample() expected = _get_voltage_offset_for_chan(0) - assert data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) def test___analog_single_channel_reader___read_many_sample___returns_valid_samples( @@ -44,7 +44,7 @@ def test___analog_single_channel_reader___read_many_sample___returns_valid_sampl assert samples_read == samples_to_read expected = _get_voltage_offset_for_chan(0) - assert data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) def test___analog_single_channel_reader___read_many_sample_with_wrong_dtype___raises_error_with_correct_dtype( @@ -88,7 +88,7 @@ def test___analog_single_channel_reader___read_waveform___returns_valid_waveform assert samples_read == samples_to_read assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(0) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert isinstance(waveform.timing.timestamp, ht_datetime) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) @@ -109,7 +109,7 @@ def test___analog_single_channel_reader___read_waveform_no_args___returns_valid_ assert samples_read == 50 assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(0) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert isinstance(waveform.timing.timestamp, ht_datetime) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) @@ -131,7 +131,7 @@ def test___analog_single_channel_reader___read_waveform_in_place___populates_val assert samples_read == samples_to_read assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(0) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert isinstance(waveform.timing.timestamp, ht_datetime) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) @@ -149,7 +149,7 @@ def _make_single_channel_reader(chan_index, offset, rate): task.ai_channels.add_ai_voltage_chan( sim_6363_device.ai_physical_chans[chan_index].name, min_val=offset, - max_val=offset + VOLTAGE_EPSILON, + max_val=offset + AI_VOLTAGE_EPSILON, ) task.timing.cfg_samp_clk_timing(rate, sample_mode=AcquisitionType.FINITE, samps_per_chan=10) return AnalogSingleChannelReader(task.in_stream) @@ -160,13 +160,13 @@ def _make_single_channel_reader(chan_index, offset, rate): reader0.read_waveform(waveform, 10) timestamp1 = waveform.timing.timestamp - assert waveform.scaled_data == pytest.approx(0, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(0, abs=AI_VOLTAGE_EPSILON) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) assert waveform.channel_name == f"{sim_6363_device.name}/ai0" reader1.read_waveform(waveform, 10) timestamp2 = waveform.timing.timestamp - assert waveform.scaled_data == pytest.approx(1, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(1, abs=AI_VOLTAGE_EPSILON) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 2000) assert waveform.channel_name == f"{sim_6363_device.name}/ai1" @@ -201,7 +201,7 @@ def test___analog_single_channel_reader___read_waveform_high_sample_rate___retur assert samples_read == samples_to_read assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(0) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert isinstance(waveform.timing.timestamp, ht_datetime) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 10_000_000) @@ -226,7 +226,7 @@ def test___analog_single_channel_reader_with_timing_flag___read_waveform___only_ assert isinstance(waveform, AnalogWaveform) assert waveform.sample_count == samples_to_read expected = _get_voltage_offset_for_chan(0) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert isinstance(waveform.timing.timestamp, ht_datetime) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval_mode == SampleIntervalMode.REGULAR @@ -251,7 +251,7 @@ def test___analog_single_channel_reader_with_extended_properties_flag___read_wav assert isinstance(waveform, AnalogWaveform) assert waveform.sample_count == samples_to_read expected = _get_voltage_offset_for_chan(0) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert waveform.timing.sample_interval_mode == SampleIntervalMode.NONE assert waveform.channel_name == ai_single_channel_task_with_timing.ai_channels[0].name assert waveform.units == "Volts" @@ -275,7 +275,7 @@ def test___analog_single_channel_reader_with_both_flags___read_waveform___includ assert isinstance(waveform, AnalogWaveform) assert waveform.sample_count == samples_to_read expected = _get_voltage_offset_for_chan(0) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert isinstance(waveform.timing.timestamp, ht_datetime) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval_mode == SampleIntervalMode.REGULAR @@ -300,7 +300,7 @@ def test___analog_single_channel_reader_with_none_flag___read_waveform___minimal assert isinstance(waveform, AnalogWaveform) assert waveform.sample_count == samples_to_read expected = _get_voltage_offset_for_chan(0) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert waveform.timing.sample_interval_mode == SampleIntervalMode.NONE assert waveform.channel_name == "" assert waveform.units == "" diff --git a/tests/component/stream_readers/test_analog_unscaled_reader.py b/tests/component/stream_readers/test_analog_unscaled_reader.py index c0d0c21c2..5d90405b4 100644 --- a/tests/component/stream_readers/test_analog_unscaled_reader.py +++ b/tests/component/stream_readers/test_analog_unscaled_reader.py @@ -8,7 +8,7 @@ import nidaqmx from nidaqmx.stream_readers import AnalogUnscaledReader -from tests.component.conftest import ( +from tests.component._analog_utils import ( VOLTAGE_CODE_EPSILON, _get_voltage_code_offset_for_chan, ) diff --git a/tests/component/stream_readers/test_digital_multi_channel_reader.py b/tests/component/stream_readers/test_digital_multi_channel_reader.py index d86dd9a18..3752e01ee 100644 --- a/tests/component/stream_readers/test_digital_multi_channel_reader.py +++ b/tests/component/stream_readers/test_digital_multi_channel_reader.py @@ -16,7 +16,7 @@ from nidaqmx.error_codes import DAQmxErrors from nidaqmx.stream_readers import DaqError, DigitalMultiChannelReader from nidaqmx.utils import flatten_channel_string -from tests.component.conftest import ( +from tests.component._digital_utils import ( _bool_array_to_int, _get_expected_data_for_line, _get_digital_data, @@ -24,9 +24,9 @@ _get_expected_digital_port_data_sample_major, _get_num_di_lines_in_task, _get_waveform_data, - _is_timestamp_close_to_now, _read_and_copy, ) +from tests.component._utils import _is_timestamp_close_to_now def test___digital_multi_channel_reader___read_one_sample_one_line___returns_valid_samples( diff --git a/tests/component/stream_readers/test_digital_single_channel_reader.py b/tests/component/stream_readers/test_digital_single_channel_reader.py index a6382d266..6fc28c186 100644 --- a/tests/component/stream_readers/test_digital_single_channel_reader.py +++ b/tests/component/stream_readers/test_digital_single_channel_reader.py @@ -16,15 +16,15 @@ from nidaqmx.error_codes import DAQmxErrors from nidaqmx.stream_readers import DaqError, DigitalSingleChannelReader from nidaqmx.utils import flatten_channel_string -from tests.component.conftest import ( +from tests.component._digital_utils import ( _bool_array_to_int, _get_expected_data_for_line, _get_digital_data, _get_num_di_lines_in_task, _get_waveform_data, - _is_timestamp_close_to_now, _read_and_copy, ) +from tests.component._utils import _is_timestamp_close_to_now def test___digital_single_channel_reader___read_one_sample_one_line___returns_valid_samples( diff --git a/tests/component/stream_readers/test_power_readers_ai.py b/tests/component/stream_readers/test_power_readers_ai.py index be5090a69..9642b2e53 100644 --- a/tests/component/stream_readers/test_power_readers_ai.py +++ b/tests/component/stream_readers/test_power_readers_ai.py @@ -13,7 +13,7 @@ PowerMultiChannelReader, PowerSingleChannelReader, ) -from tests.component.conftest import ( +from tests.component._analog_utils import ( POWER_BINARY_EPSILON, POWER_EPSILON, _get_current_code_setpoint_for_chan, diff --git a/tests/component/stream_writers/test_analog_multi_channel_writer.py b/tests/component/stream_writers/test_analog_multi_channel_writer.py index fb660d3f1..2a70b46f8 100644 --- a/tests/component/stream_writers/test_analog_multi_channel_writer.py +++ b/tests/component/stream_writers/test_analog_multi_channel_writer.py @@ -7,9 +7,9 @@ import nidaqmx from nidaqmx.stream_writers import AnalogMultiChannelWriter -from tests.component.conftest import ( +from tests.component._analog_utils import ( _get_expected_voltage_for_chan, - VOLTAGE_EPSILON, + AO_VOLTAGE_EPSILON, ) @@ -24,7 +24,7 @@ def test___analog_multi_channel_writer___write_one_sample___updates_output( writer.write_one_sample(data) - assert ai_multi_channel_loopback_task.read() == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert ai_multi_channel_loopback_task.read() == pytest.approx(expected, abs=AO_VOLTAGE_EPSILON) def test___analog_multi_channel_writer___write_one_sample_with_wrong_dtype___raises_error_with_correct_dtype( @@ -63,7 +63,7 @@ def test___analog_multi_channel_writer___write_many_sample___updates_output( samples_written = writer.write_many_sample(data) assert samples_written == samples_to_write - assert ai_multi_channel_loopback_task.read() == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert ai_multi_channel_loopback_task.read() == pytest.approx(expected, abs=AO_VOLTAGE_EPSILON) def test___analog_multi_channel_writer___write_many_sample_with_wrong_dtype___raises_error_with_correct_dtype( diff --git a/tests/component/stream_writers/test_analog_single_channel_writer.py b/tests/component/stream_writers/test_analog_single_channel_writer.py index 063d15d6d..51b363a70 100644 --- a/tests/component/stream_writers/test_analog_single_channel_writer.py +++ b/tests/component/stream_writers/test_analog_single_channel_writer.py @@ -7,9 +7,9 @@ import nidaqmx from nidaqmx.stream_writers import AnalogSingleChannelWriter -from tests.component.conftest import ( +from tests.component._analog_utils import ( _get_expected_voltage_for_chan, - VOLTAGE_EPSILON, + AO_VOLTAGE_EPSILON, ) @@ -22,7 +22,7 @@ def test___analog_single_channel_writer___write_one_sample___updates_output( writer.write_one_sample(expected) - assert ai_single_channel_loopback_task.read() == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert ai_single_channel_loopback_task.read() == pytest.approx(expected, abs=AO_VOLTAGE_EPSILON) def test___analog_single_channel_writer___write_many_sample___updates_output( @@ -38,7 +38,7 @@ def test___analog_single_channel_writer___write_many_sample___updates_output( samples_written = writer.write_many_sample(data) assert samples_written == samples_to_write - assert ai_single_channel_loopback_task.read() == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert ai_single_channel_loopback_task.read() == pytest.approx(expected, abs=AO_VOLTAGE_EPSILON) def test___analog_single_channel_writer___write_many_sample_with_wrong_dtype___raises_error_with_correct_dtype( diff --git a/tests/component/stream_writers/test_analog_unscaled_writer.py b/tests/component/stream_writers/test_analog_unscaled_writer.py index da6525439..c677e4f95 100644 --- a/tests/component/stream_writers/test_analog_unscaled_writer.py +++ b/tests/component/stream_writers/test_analog_unscaled_writer.py @@ -7,10 +7,10 @@ import nidaqmx from nidaqmx.stream_writers import AnalogUnscaledWriter -from tests.component.conftest import ( +from tests.component._analog_utils import ( + RAW_VOLTAGE_EPSILON, _get_expected_voltage_for_chan, _volts_to_codes, - VOLTAGE_EPSILON_FOR_RAW, ) @@ -37,9 +37,7 @@ def test___analog_unscaled_writer___write_int16___updates_output( samples_written = writer.write_int16(data) assert samples_written == samples_to_write - assert ai_multi_channel_loopback_task.read() == pytest.approx( - expected, abs=VOLTAGE_EPSILON_FOR_RAW - ) + assert ai_multi_channel_loopback_task.read() == pytest.approx(expected, abs=RAW_VOLTAGE_EPSILON) def test___analog_unscaled_writer___write_int16_with_wrong_dtype___raises_error_with_correct_dtype( @@ -79,9 +77,7 @@ def test___analog_unscaled_writer___write_int32___updates_output( samples_written = writer.write_int32(data) assert samples_written == samples_to_write - assert ai_multi_channel_loopback_task.read() == pytest.approx( - expected, abs=VOLTAGE_EPSILON_FOR_RAW - ) + assert ai_multi_channel_loopback_task.read() == pytest.approx(expected, abs=RAW_VOLTAGE_EPSILON) def test___analog_unscaled_writer___write_int32_with_wrong_dtype___raises_error_with_correct_dtype( @@ -121,9 +117,7 @@ def test___analog_unscaled_writer___write_uint16___updates_output( samples_written = writer.write_uint16(data) assert samples_written == samples_to_write - assert ai_multi_channel_loopback_task.read() == pytest.approx( - expected, abs=VOLTAGE_EPSILON_FOR_RAW - ) + assert ai_multi_channel_loopback_task.read() == pytest.approx(expected, abs=RAW_VOLTAGE_EPSILON) def test___analog_unscaled_writer___write_uint16_with_wrong_dtype___raises_error_with_correct_dtype( @@ -163,9 +157,7 @@ def test___analog_unscaled_writer___write_uint32___updates_output( samples_written = writer.write_uint32(data) assert samples_written == samples_to_write - assert ai_multi_channel_loopback_task.read() == pytest.approx( - expected, abs=VOLTAGE_EPSILON_FOR_RAW - ) + assert ai_multi_channel_loopback_task.read() == pytest.approx(expected, abs=RAW_VOLTAGE_EPSILON) def test___analog_unscaled_writer___write_uint32_with_wrong_dtype___raises_error_with_correct_dtype( diff --git a/tests/component/stream_writers/test_digital_multi_channel_writer.py b/tests/component/stream_writers/test_digital_multi_channel_writer.py index f2569727d..a7c67044a 100644 --- a/tests/component/stream_writers/test_digital_multi_channel_writer.py +++ b/tests/component/stream_writers/test_digital_multi_channel_writer.py @@ -11,7 +11,7 @@ import nidaqmx.system from nidaqmx.constants import LineGrouping from nidaqmx.stream_writers import DigitalMultiChannelWriter -from tests.component.conftest import ( +from tests.component._digital_utils import ( _get_digital_data, _get_digital_port_data_for_sample, _get_digital_port_data_port_major, diff --git a/tests/component/stream_writers/test_digital_single_channel_writer.py b/tests/component/stream_writers/test_digital_single_channel_writer.py index adfa713b5..2c49be72b 100644 --- a/tests/component/stream_writers/test_digital_single_channel_writer.py +++ b/tests/component/stream_writers/test_digital_single_channel_writer.py @@ -8,7 +8,7 @@ import nidaqmx from nidaqmx.stream_writers import DigitalSingleChannelWriter -from tests.component.conftest import ( +from tests.component._digital_utils import ( _get_digital_data, _get_num_do_lines_in_task, _int_to_bool_array, diff --git a/tests/component/task/test_task_read_ai.py b/tests/component/task/test_task_read_ai.py index c8809f4d7..614f0cac1 100644 --- a/tests/component/task/test_task_read_ai.py +++ b/tests/component/task/test_task_read_ai.py @@ -4,9 +4,9 @@ import nidaqmx from nidaqmx.constants import AcquisitionType -from tests.component.conftest import ( +from tests.component._analog_utils import ( POWER_EPSILON, - VOLTAGE_EPSILON, + AI_VOLTAGE_EPSILON, _assert_equal_2d, _get_current_setpoint_for_chan, _get_voltage_offset_for_chan, @@ -20,7 +20,7 @@ def test___analog_single_channel___read_unset_samples___returns_valid_scalar( data = ai_single_channel_task.read() expected = _get_voltage_offset_for_chan(0) - assert data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) def test___analog_single_channel___read_one_sample___returns_valid_1d_samples( @@ -29,7 +29,7 @@ def test___analog_single_channel___read_one_sample___returns_valid_1d_samples( data = ai_single_channel_task.read(1) expected = [_get_voltage_offset_for_chan(0)] - assert data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) def test___analog_single_channel___read_many_sample___returns_valid_1d_samples( @@ -40,7 +40,7 @@ def test___analog_single_channel___read_many_sample___returns_valid_1d_samples( data = ai_single_channel_task.read(samples_to_read) expected = [_get_voltage_offset_for_chan(0) for _ in range(samples_to_read)] - assert data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) def test___analog_single_channel_finite___read_too_many_sample___returns_valid_1d_samples_truncated( @@ -55,7 +55,7 @@ def test___analog_single_channel_finite___read_too_many_sample___returns_valid_1 data = ai_single_channel_task.read(samples_to_read) expected = [_get_voltage_offset_for_chan(0) for _ in range(samples_to_acquire)] - assert data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) def test___analog_multi_channel___read_unset_samples___returns_1d_channels( @@ -66,7 +66,7 @@ def test___analog_multi_channel___read_unset_samples___returns_1d_channels( data = ai_multi_channel_task.read() expected = [_get_voltage_offset_for_chan(chan_index) for chan_index in range(num_channels)] - assert data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) def test___analog_multi_channel___read_one_sample___returns_valid_2d_channels_samples( @@ -77,7 +77,7 @@ def test___analog_multi_channel___read_one_sample___returns_valid_2d_channels_sa data = ai_multi_channel_task.read(1) expected = [[_get_voltage_offset_for_chan(chan_index)] for chan_index in range(num_channels)] - _assert_equal_2d(data, expected, abs=VOLTAGE_EPSILON) + _assert_equal_2d(data, expected, abs=AI_VOLTAGE_EPSILON) def test___analog_multi_channel___read_many_sample___returns_valid_2d_channels_samples( @@ -92,7 +92,7 @@ def test___analog_multi_channel___read_many_sample___returns_valid_2d_channels_s [_get_voltage_offset_for_chan(chan_index) for _ in range(samples_to_read)] for chan_index in range(num_channels) ] - _assert_equal_2d(data, expected, abs=VOLTAGE_EPSILON) + _assert_equal_2d(data, expected, abs=AI_VOLTAGE_EPSILON) @pytest.mark.xfail( @@ -115,7 +115,7 @@ def test___analog_multi_channel_finite___read_too_many_sample___returns_valid_2d [_get_voltage_offset_for_chan(chan_index) for _ in range(samples_to_acquire)] for chan_index in range(num_channels) ] - _assert_equal_2d(data, expected, abs=VOLTAGE_EPSILON) + _assert_equal_2d(data, expected, abs=AI_VOLTAGE_EPSILON) def test___power_single_channel___read_unset_samples___returns_valid_scalar( diff --git a/tests/component/task/test_task_read_waveform_ai.py b/tests/component/task/test_task_read_waveform_ai.py index adf70616c..7580362fd 100644 --- a/tests/component/task/test_task_read_waveform_ai.py +++ b/tests/component/task/test_task_read_waveform_ai.py @@ -4,8 +4,8 @@ from nitypes.waveform import AnalogWaveform import nidaqmx -from tests.component.conftest import ( - VOLTAGE_EPSILON, +from tests.component._analog_utils import ( + AI_VOLTAGE_EPSILON, _get_voltage_offset_for_chan, ) @@ -19,7 +19,7 @@ def test___analog_single_channel___read_waveform___returns_valid_waveform( assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(0) assert waveform.sample_count == 50 - assert waveform.raw_data[0] == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.raw_data[0] == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) @pytest.mark.grpc_skip(reason="read_analog_waveform not implemented in GRPC") @@ -31,7 +31,7 @@ def test___analog_single_channel___read_waveform_one_sample___returns_waveform_w assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(0) assert waveform.sample_count == 1 - assert waveform.raw_data[0] == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.raw_data[0] == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) @pytest.mark.grpc_skip(reason="read_analog_waveform not implemented in GRPC") @@ -45,7 +45,7 @@ def test___analog_single_channel___read_waveform_many_sample___returns_waveform_ assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(0) assert waveform.sample_count == samples_to_read - assert waveform.raw_data[0] == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.raw_data[0] == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) @pytest.mark.xfail( @@ -64,7 +64,7 @@ def test___analog_single_channel_finite___read_waveform_too_many_samples___retur assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(0) assert waveform.sample_count == samples_available - assert waveform.raw_data[0] == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.raw_data[0] == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) @pytest.mark.grpc_skip(reason="read_analog_waveform not implemented in GRPC") @@ -81,7 +81,7 @@ def test___analog_multi_channel___read_waveform___returns_valid_waveforms( for chan_index, waveform in enumerate(waveforms): expected = _get_voltage_offset_for_chan(chan_index) assert waveform.sample_count == 50 - assert waveform.raw_data[0] == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.raw_data[0] == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) @pytest.mark.grpc_skip(reason="read_analog_waveform not implemented in GRPC") @@ -98,7 +98,7 @@ def test___analog_multi_channel___read_waveform_one_sample___returns_waveforms_w for chan_index, waveform in enumerate(waveforms): expected = _get_voltage_offset_for_chan(chan_index) assert waveform.sample_count == 1 - assert waveform.raw_data[0] == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.raw_data[0] == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) @pytest.mark.grpc_skip(reason="read_analog_waveform not implemented in GRPC") @@ -116,7 +116,7 @@ def test___analog_multi_channel___read_waveform_many_samples___returns_waveforms for chan_index, waveform in enumerate(waveforms): expected = _get_voltage_offset_for_chan(chan_index) assert waveform.sample_count == samples_to_read - assert waveform.raw_data[0] == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.raw_data[0] == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) @pytest.mark.xfail( @@ -138,4 +138,4 @@ def test___analog_multi_channel_finite___read_waveform_too_many_samples___return for chan_index, waveform in enumerate(waveforms): expected = _get_voltage_offset_for_chan(chan_index) assert waveform.sample_count == samples_available - assert waveform.raw_data[0] == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.raw_data[0] == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) diff --git a/tests/component/task/test_task_read_waveform_di.py b/tests/component/task/test_task_read_waveform_di.py index 9ff283f61..d9adb26db 100644 --- a/tests/component/task/test_task_read_waveform_di.py +++ b/tests/component/task/test_task_read_waveform_di.py @@ -5,7 +5,7 @@ import nidaqmx import nidaqmx.system -from tests.component.conftest import ( +from tests.component._digital_utils import ( _get_expected_data_for_line, _get_waveform_data, ) From 9cc7dd6f21581badeba47f74a7e34e0d70ed0b6d Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Wed, 10 Sep 2025 10:36:30 -0500 Subject: [PATCH 4/9] poetry run nps fix --- tests/component/_analog_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/component/_analog_utils.py b/tests/component/_analog_utils.py index 62b7d8071..4dec858f5 100644 --- a/tests/component/_analog_utils.py +++ b/tests/component/_analog_utils.py @@ -55,6 +55,7 @@ def _assert_equal_2d(data: list[list[float]], expected: list[list[float]], abs: for i in range(len(data)): assert data[i] == pytest.approx(expected[i], abs=abs) + # NOTE: We use simulated signals for AI validation, so we can be fairly strict here. AI_VOLTAGE_EPSILON = 1e-3 From f9bd5356442fb18ad22df203ea2fdd112e417fbc Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Wed, 10 Sep 2025 10:43:01 -0500 Subject: [PATCH 5/9] poke test_analog_multi_channel_reader.py to try to fix the CI --- .../stream_readers/test_analog_multi_channel_reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/component/stream_readers/test_analog_multi_channel_reader.py b/tests/component/stream_readers/test_analog_multi_channel_reader.py index c26eb2075..7b5b0f009 100644 --- a/tests/component/stream_readers/test_analog_multi_channel_reader.py +++ b/tests/component/stream_readers/test_analog_multi_channel_reader.py @@ -22,7 +22,7 @@ def test___analog_multi_channel_reader___read_one_sample___returns_valid_samples( ai_multi_channel_task: nidaqmx.Task, -) -> None: +) -> None: reader = AnalogMultiChannelReader(ai_multi_channel_task.in_stream) num_channels = ai_multi_channel_task.number_of_channels data = numpy.full(num_channels, math.inf, dtype=numpy.float64) From 0e5d505fcc991b2bef6fbd262f5d988fdebcbf30 Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Wed, 10 Sep 2025 10:45:46 -0500 Subject: [PATCH 6/9] poking again to see what the CI does --- .../stream_readers/test_analog_multi_channel_reader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/component/stream_readers/test_analog_multi_channel_reader.py b/tests/component/stream_readers/test_analog_multi_channel_reader.py index 7b5b0f009..e599a6552 100644 --- a/tests/component/stream_readers/test_analog_multi_channel_reader.py +++ b/tests/component/stream_readers/test_analog_multi_channel_reader.py @@ -22,7 +22,7 @@ def test___analog_multi_channel_reader___read_one_sample___returns_valid_samples( ai_multi_channel_task: nidaqmx.Task, -) -> None: +) -> None: reader = AnalogMultiChannelReader(ai_multi_channel_task.in_stream) num_channels = ai_multi_channel_task.number_of_channels data = numpy.full(num_channels, math.inf, dtype=numpy.float64) @@ -224,7 +224,7 @@ def test___analog_multi_channel_reader_with_timing_flag___read_waveforms___only_ ai_multi_channel_task_with_timing: nidaqmx.Task, ) -> None: in_stream = ai_multi_channel_task_with_timing.in_stream - in_stream.waveform_attribute_mode = WaveformAttributeMode.TIMING + in_stream.waveform_attribute_mode = WaveformAttributeMode.TIMINGz reader = AnalogMultiChannelReader(in_stream) num_channels = ai_multi_channel_task_with_timing.number_of_channels samples_to_read = 10 From deb610f25626c06502fc193838ecedfa2f27f5a7 Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Wed, 10 Sep 2025 10:48:43 -0500 Subject: [PATCH 7/9] undo poking --- .../stream_readers/test_analog_multi_channel_reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/component/stream_readers/test_analog_multi_channel_reader.py b/tests/component/stream_readers/test_analog_multi_channel_reader.py index e599a6552..c26eb2075 100644 --- a/tests/component/stream_readers/test_analog_multi_channel_reader.py +++ b/tests/component/stream_readers/test_analog_multi_channel_reader.py @@ -224,7 +224,7 @@ def test___analog_multi_channel_reader_with_timing_flag___read_waveforms___only_ ai_multi_channel_task_with_timing: nidaqmx.Task, ) -> None: in_stream = ai_multi_channel_task_with_timing.in_stream - in_stream.waveform_attribute_mode = WaveformAttributeMode.TIMINGz + in_stream.waveform_attribute_mode = WaveformAttributeMode.TIMING reader = AnalogMultiChannelReader(in_stream) num_channels = ai_multi_channel_task_with_timing.number_of_channels samples_to_read = 10 From d8042a3d80c30b69592c198b310265a0707fe7d5 Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Wed, 10 Sep 2025 12:06:50 -0500 Subject: [PATCH 8/9] fix merge --- .../test_analog_multi_channel_reader.py | 18 +++++++++--------- .../test_analog_single_channel_reader.py | 10 +++++----- .../test_digital_multi_channel_reader.py | 2 +- .../test_digital_single_channel_reader.py | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/component/stream_readers/test_analog_multi_channel_reader.py b/tests/component/stream_readers/test_analog_multi_channel_reader.py index 2103b7af0..57a4d7b59 100644 --- a/tests/component/stream_readers/test_analog_multi_channel_reader.py +++ b/tests/component/stream_readers/test_analog_multi_channel_reader.py @@ -224,7 +224,7 @@ def test___analog_multi_channel_reader___read_into_undersized_waveforms___return for chan_index, waveform in enumerate(waveforms): assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(chan_index) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert isinstance(waveform.timing.timestamp, ht_datetime) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) @@ -244,12 +244,12 @@ def _make_multi_channel_reader(chan_a_index, chan_b_index, samps_per_chan): task.ai_channels.add_ai_voltage_chan( sim_6363_device.ai_physical_chans[chan_a_index].name, min_val=chan_a_index, - max_val=chan_a_index + VOLTAGE_EPSILON, + max_val=chan_a_index + AI_VOLTAGE_EPSILON, ) task.ai_channels.add_ai_voltage_chan( sim_6363_device.ai_physical_chans[chan_b_index].name, min_val=chan_b_index, - max_val=chan_b_index + VOLTAGE_EPSILON, + max_val=chan_b_index + AI_VOLTAGE_EPSILON, ) task.timing.cfg_samp_clk_timing( 1000.0, sample_mode=AcquisitionType.FINITE, samps_per_chan=samps_per_chan @@ -266,26 +266,26 @@ def _make_multi_channel_reader(chan_a_index, chan_b_index, samps_per_chan): reader0.read_waveforms(waveforms, 5) assert waveforms[0].sample_count == 5 - assert waveforms[0].scaled_data == pytest.approx(0, abs=VOLTAGE_EPSILON) + assert waveforms[0].scaled_data == pytest.approx(0, abs=AI_VOLTAGE_EPSILON) assert waveforms[0].channel_name == f"{sim_6363_device.name}/ai0" assert waveforms[1].sample_count == 5 - assert waveforms[1].scaled_data == pytest.approx(1, abs=VOLTAGE_EPSILON) + assert waveforms[1].scaled_data == pytest.approx(1, abs=AI_VOLTAGE_EPSILON) assert waveforms[1].channel_name == f"{sim_6363_device.name}/ai1" reader1.read_waveforms(waveforms, 10) assert waveforms[0].sample_count == 10 - assert waveforms[0].scaled_data == pytest.approx(2, abs=VOLTAGE_EPSILON) + assert waveforms[0].scaled_data == pytest.approx(2, abs=AI_VOLTAGE_EPSILON) assert waveforms[0].channel_name == f"{sim_6363_device.name}/ai2" assert waveforms[1].sample_count == 10 - assert waveforms[1].scaled_data == pytest.approx(3, abs=VOLTAGE_EPSILON) + assert waveforms[1].scaled_data == pytest.approx(3, abs=AI_VOLTAGE_EPSILON) assert waveforms[1].channel_name == f"{sim_6363_device.name}/ai3" reader2.read_waveforms(waveforms, 15) assert waveforms[0].sample_count == 15 - assert waveforms[0].scaled_data == pytest.approx(4, abs=VOLTAGE_EPSILON) + assert waveforms[0].scaled_data == pytest.approx(4, abs=AI_VOLTAGE_EPSILON) assert waveforms[0].channel_name == f"{sim_6363_device.name}/ai4" assert waveforms[1].sample_count == 15 - assert waveforms[1].scaled_data == pytest.approx(5, abs=VOLTAGE_EPSILON) + assert waveforms[1].scaled_data == pytest.approx(5, abs=AI_VOLTAGE_EPSILON) assert waveforms[1].channel_name == f"{sim_6363_device.name}/ai5" diff --git a/tests/component/stream_readers/test_analog_single_channel_reader.py b/tests/component/stream_readers/test_analog_single_channel_reader.py index e5268b55f..6387dcd13 100644 --- a/tests/component/stream_readers/test_analog_single_channel_reader.py +++ b/tests/component/stream_readers/test_analog_single_channel_reader.py @@ -201,7 +201,7 @@ def test___analog_single_channel_reader___read_into_undersized_waveform___return assert samples_read == samples_to_read assert isinstance(waveform, AnalogWaveform) expected = _get_voltage_offset_for_chan(0) - assert waveform.scaled_data == pytest.approx(expected, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(expected, abs=AI_VOLTAGE_EPSILON) assert isinstance(waveform.timing.timestamp, ht_datetime) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) @@ -219,7 +219,7 @@ def _make_single_channel_reader(chan_index, offset, samps_per_chan): task.ai_channels.add_ai_voltage_chan( sim_6363_device.ai_physical_chans[chan_index].name, min_val=offset, - max_val=offset + VOLTAGE_EPSILON, + max_val=offset + AI_VOLTAGE_EPSILON, ) task.timing.cfg_samp_clk_timing( 1000.0, sample_mode=AcquisitionType.FINITE, samps_per_chan=samps_per_chan @@ -233,17 +233,17 @@ def _make_single_channel_reader(chan_index, offset, samps_per_chan): reader0.read_waveform(waveform, 5) assert waveform.sample_count == 5 - assert waveform.scaled_data == pytest.approx(0, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(0, abs=AI_VOLTAGE_EPSILON) assert waveform.channel_name == f"{sim_6363_device.name}/ai0" reader1.read_waveform(waveform, 10) assert waveform.sample_count == 10 - assert waveform.scaled_data == pytest.approx(1, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(1, abs=AI_VOLTAGE_EPSILON) assert waveform.channel_name == f"{sim_6363_device.name}/ai1" reader2.read_waveform(waveform, 15) assert waveform.sample_count == 15 - assert waveform.scaled_data == pytest.approx(2, abs=VOLTAGE_EPSILON) + assert waveform.scaled_data == pytest.approx(2, abs=AI_VOLTAGE_EPSILON) assert waveform.channel_name == f"{sim_6363_device.name}/ai2" diff --git a/tests/component/stream_readers/test_digital_multi_channel_reader.py b/tests/component/stream_readers/test_digital_multi_channel_reader.py index 078a7c39d..588799296 100644 --- a/tests/component/stream_readers/test_digital_multi_channel_reader.py +++ b/tests/component/stream_readers/test_digital_multi_channel_reader.py @@ -601,7 +601,7 @@ def test___digital_multi_channel_multi_line_reader___read_into_undersized_wavefo ) -> None: reader = DigitalMultiChannelReader(di_multi_chan_multi_line_timing_task.in_stream) num_channels = di_multi_chan_multi_line_timing_task.number_of_channels - num_lines = _get_num_lines_in_task(di_multi_chan_multi_line_timing_task) + num_lines = _get_num_di_lines_in_task(di_multi_chan_multi_line_timing_task) samples_to_read = 10 waveforms = [DigitalWaveform(samples_to_read - 1) for _ in range(num_channels)] diff --git a/tests/component/stream_readers/test_digital_single_channel_reader.py b/tests/component/stream_readers/test_digital_single_channel_reader.py index 8170e16b9..0b103629d 100644 --- a/tests/component/stream_readers/test_digital_single_channel_reader.py +++ b/tests/component/stream_readers/test_digital_single_channel_reader.py @@ -414,7 +414,7 @@ def test___digital_single_line_reader___read_into_undersized_waveform___returns_ samples_read = reader.read_waveform(waveform, samples_to_read) assert samples_read == samples_to_read - assert _get_waveform_data(waveform) == _get_expected_digital_data(1, samples_to_read) + assert _get_waveform_data(waveform) == _get_digital_data(1, samples_to_read) assert _is_timestamp_close_to_now(waveform.timing.timestamp) assert waveform.timing.sample_interval == ht_timedelta(seconds=1 / 1000) assert waveform.timing.sample_interval_mode == SampleIntervalMode.REGULAR From fd596ac4e99c06de0b689563200d9da37d3830d1 Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Wed, 10 Sep 2025 12:50:31 -0500 Subject: [PATCH 9/9] import nidaqmx.system --- .../component/stream_readers/test_analog_multi_channel_reader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/component/stream_readers/test_analog_multi_channel_reader.py b/tests/component/stream_readers/test_analog_multi_channel_reader.py index 57a4d7b59..8017d45fa 100644 --- a/tests/component/stream_readers/test_analog_multi_channel_reader.py +++ b/tests/component/stream_readers/test_analog_multi_channel_reader.py @@ -10,6 +10,7 @@ from nitypes.waveform import AnalogWaveform, SampleIntervalMode import nidaqmx +import nidaqmx.system from nidaqmx._feature_toggles import WAVEFORM_SUPPORT, FeatureNotSupportedError from nidaqmx.constants import AcquisitionType, ReallocationPolicy, WaveformAttributeMode from nidaqmx.error_codes import DAQmxErrors