From 8887f0a71a6a3e04bd0bf577b3a20718fb867ba3 Mon Sep 17 00:00:00 2001 From: NI-velmurugan Date: Tue, 20 Oct 2026 08:36:31 +0000 Subject: [PATCH 1/4] Add NI-DMM continupus aqu exter trigger example --- ...idmm_cont_acq_multiple_samples_ext_trig.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/nidmm/examples/nidmm_cont_acq_multiple_samples_ext_trig.py diff --git a/src/nidmm/examples/nidmm_cont_acq_multiple_samples_ext_trig.py b/src/nidmm/examples/nidmm_cont_acq_multiple_samples_ext_trig.py new file mode 100644 index 000000000..737910932 --- /dev/null +++ b/src/nidmm/examples/nidmm_cont_acq_multiple_samples_ext_trig.py @@ -0,0 +1,87 @@ +#!/usr/bin/python + +import argparse +import collections +import nidmm +import pytest +import sys + + +# Number of most recent measurements kept on the chart +CHART_HISTORY_SIZE = 250 + + +def example(resource_name, options, function, measurement_range, digits, trigger_source, fetch_count): + # Imported lazily because matplotlib is unavailable on some platforms (e.g. 32-bit Python) + import matplotlib.pyplot as plt + + with nidmm.Session(resource_name=resource_name, options=options) as session: + session.configure_measurement_digits(measurement_function=nidmm.Function[function], range=measurement_range, resolution_digits=digits) + session.configure_trigger(trigger_source=nidmm.TriggerSource[trigger_source]) + # The sample trigger uses the same source as the trigger; a sample_count of 0 configures a continuous acquisition + session.configure_multi_point(trigger_count=1, sample_count=0, sample_trigger=nidmm.SampleTrigger[trigger_source]) + + figure, axes = plt.subplots() + (line,) = axes.plot([]) + axes.set_title('Measurements') + axes.set_xlabel('Samples') + axes.set_ylabel('Measured value') + axes.grid(True) + + measurements = collections.deque(maxlen=CHART_HISTORY_SIZE) + total_samples = 0 + fetches = 0 + try: + with session.initiate(): + # A fetch_count of None runs until the chart window is closed or Ctrl+C is pressed + while plt.fignum_exists(figure.number) and (fetch_count is None or fetches < fetch_count): + backlog, _ = session.read_status() + if backlog > 0: + new_samples = session.fetch_multi_point(array_size=backlog) + measurements.extend(new_samples) + total_samples += len(new_samples) + first_sample = total_samples - len(measurements) + line.set_data(range(first_sample, total_samples), measurements) + axes.relim() + axes.autoscale_view() + fetches += 1 + plt.pause(0.001) + except KeyboardInterrupt: + pass + finally: + plt.close(figure) + + +def _main(argsv): + parser = argparse.ArgumentParser(description='Continuously acquires measurements on each trigger and displays them on a chart using the NI-DMM API.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('-n', '--resource-name', default='PXI1Slot2', help='Contains the resource_name of the device to initialize.') + parser.add_argument('-f', '--function', default='DC_VOLTS', choices=nidmm.Function.__members__.keys(), type=str.upper, help='Specifies the measurement_function used to acquire the measurement.') + parser.add_argument('-r', '--range', default=10, type=float, help='Specifies the range for the function specified in the Measurement_Function parameter.') + parser.add_argument('-d', '--digits', default=5.5, type=float, help='Specifies the resolution of the measurement in digits.') + parser.add_argument('-t', '--trigger-source', default='EXTERNAL', choices=nidmm.TriggerSource.__members__.keys(), type=str.upper, help='Specifies the trigger source that initiates the acquisition.') + parser.add_argument('--fetch-count', default=None, type=int, help='Stops after this many fetches; omit to run until the chart window is closed or Ctrl+C is pressed.') + parser.add_argument('-op', '--option-string', default='', type=str, help='Sets the initial value of certain attributes for the session.') + args = parser.parse_args(argsv) + example(args.resource_name, args.option_string, args.function, args.range, args.digits, args.trigger_source, args.fetch_count) + + +def main(): + _main(sys.argv[1:]) + + +def test_example(): + plt = pytest.importorskip('matplotlib.pyplot') + plt.switch_backend('Agg') # Avoid opening a chart window during the test + options = {'simulate': True, 'driver_setup': {'Model': '4082', 'BoardType': 'PXIe', }, } + example('PXI1Slot2', options, 'DC_VOLTS', 10, 5.5, 'IMMEDIATE', 2) + + +def test_main(): + plt = pytest.importorskip('matplotlib.pyplot') + plt.switch_backend('Agg') # Avoid opening a chart window during the test + cmd_line = ['--option-string', 'Simulate=1, DriverSetup=Model:4082; BoardType:PXIe', '--trigger-source', 'IMMEDIATE', '--fetch-count', '2', ] + _main(cmd_line) + + +if __name__ == '__main__': + main() From 2aded4ad895cdffc48a547916c463fad545847fb Mon Sep 17 00:00:00 2001 From: NI-velmurugan Date: Wed, 21 Oct 2026 08:00:07 +0000 Subject: [PATCH 2/4] Adding Continuous Acquisition Multiple Sample External Trigger example in NI-DMM --- CHANGELOG.md | 1 + docs/nidmm/examples.rst | 9 +++ ...idmm_cont_acq_multiple_samples_ext_trig.py | 74 +++++++++++++------ 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86a8d9eb3..e6fd8cce2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -799,6 +799,7 @@ #### [nidmm] Unreleased - Added - Added the `nidmm_measure_ac_volts.py` example. + - Added the `nidmm_cont_acq_multiple_samples_ext_trig.py` example. - (Common) Added `nitlsconfig[grpc]>=1.0.0a2` to the optional gRPC dependencies. - Changed - Removed diff --git a/docs/nidmm/examples.rst b/docs/nidmm/examples.rst index ec6b3ee3e..8fe71bbe7 100644 --- a/docs/nidmm/examples.rst +++ b/docs/nidmm/examples.rst @@ -3,6 +3,15 @@ Examples `You can download all nidmm examples for latest version here `_ +nidmm_cont_acq_multiple_samples_ext_trig.py +------------------------------------------- + +.. literalinclude:: ../../src/nidmm/examples/nidmm_cont_acq_multiple_samples_ext_trig.py + :language: python + :linenos: + :encoding: utf8 + :caption: `(nidmm_cont_acq_multiple_samples_ext_trig.py) `_ + nidmm_fetch_waveform.py ----------------------- diff --git a/src/nidmm/examples/nidmm_cont_acq_multiple_samples_ext_trig.py b/src/nidmm/examples/nidmm_cont_acq_multiple_samples_ext_trig.py index 737910932..d25042eae 100644 --- a/src/nidmm/examples/nidmm_cont_acq_multiple_samples_ext_trig.py +++ b/src/nidmm/examples/nidmm_cont_acq_multiple_samples_ext_trig.py @@ -11,41 +11,67 @@ CHART_HISTORY_SIZE = 250 -def example(resource_name, options, function, measurement_range, digits, trigger_source, fetch_count): +def _create_chart(plt): + figure, axes = plt.subplots() + (line,) = axes.plot([]) + axes.set_title('Measurements') + axes.set_xlabel('Samples') + axes.set_ylabel('Measured value') + axes.grid(True) + return figure, axes, line + + +def _update_chart(plt, axes, line, measurements, new_samples, samples_fetched): + measurements.extend(new_samples) + samples_fetched += len(new_samples) + # The x-axis keeps absolute sample numbers, so it starts after the samples dropped from the history + first_sample = samples_fetched - len(measurements) + line.set_data(range(first_sample, samples_fetched), measurements) + axes.relim() + axes.autoscale_view() + plt.pause(0.001) + return samples_fetched + + +def example(resource_name, options, function, measurement_range, digits, trigger_source, total_samples): # Imported lazily because matplotlib is unavailable on some platforms (e.g. 32-bit Python) import matplotlib.pyplot as plt with nidmm.Session(resource_name=resource_name, options=options) as session: session.configure_measurement_digits(measurement_function=nidmm.Function[function], range=measurement_range, resolution_digits=digits) + # Trigger and sample trigger slope are not configured: the slope properties and enums are not in the Python API (#875) session.configure_trigger(trigger_source=nidmm.TriggerSource[trigger_source]) # The sample trigger uses the same source as the trigger; a sample_count of 0 configures a continuous acquisition session.configure_multi_point(trigger_count=1, sample_count=0, sample_trigger=nidmm.SampleTrigger[trigger_source]) - figure, axes = plt.subplots() - (line,) = axes.plot([]) - axes.set_title('Measurements') - axes.set_xlabel('Samples') - axes.set_ylabel('Measured value') - axes.grid(True) - + # Create an empty chart; the line is filled in as samples arrive + figure, axes, line = _create_chart(plt) + # Only the most recent CHART_HISTORY_SIZE measurements are kept measurements = collections.deque(maxlen=CHART_HISTORY_SIZE) - total_samples = 0 - fetches = 0 + + samples_fetched = 0 try: with session.initiate(): - # A fetch_count of None runs until the chart window is closed or Ctrl+C is pressed - while plt.fignum_exists(figure.number) and (fetch_count is None or fetches < fetch_count): + # A total_samples of None runs until the chart window is closed or Ctrl+C is pressed + while plt.fignum_exists(figure.number) and ( + total_samples is None or samples_fetched < total_samples + ): backlog, _ = session.read_status() if backlog > 0: - new_samples = session.fetch_multi_point(array_size=backlog) - measurements.extend(new_samples) - total_samples += len(new_samples) - first_sample = total_samples - len(measurements) - line.set_data(range(first_sample, total_samples), measurements) - axes.relim() - axes.autoscale_view() - fetches += 1 + # The last fetch only takes the samples still needed to reach total_samples + samples_in_this_fetch = backlog + if total_samples is not None: + samples_in_this_fetch = min(backlog, total_samples - samples_fetched) + + new_samples = session.fetch_multi_point(array_size=samples_in_this_fetch) + + # Add the new samples to the chart and get the updated total sample count + samples_fetched = _update_chart(plt, axes, line, measurements, new_samples, samples_fetched) plt.pause(0.001) + + # The acquisition has stopped; keep the final chart on screen until its window is closed + if total_samples is not None and plt.fignum_exists(figure.number): + plt.show() except KeyboardInterrupt: pass finally: @@ -59,10 +85,10 @@ def _main(argsv): parser.add_argument('-r', '--range', default=10, type=float, help='Specifies the range for the function specified in the Measurement_Function parameter.') parser.add_argument('-d', '--digits', default=5.5, type=float, help='Specifies the resolution of the measurement in digits.') parser.add_argument('-t', '--trigger-source', default='EXTERNAL', choices=nidmm.TriggerSource.__members__.keys(), type=str.upper, help='Specifies the trigger source that initiates the acquisition.') - parser.add_argument('--fetch-count', default=None, type=int, help='Stops after this many fetches; omit to run until the chart window is closed or Ctrl+C is pressed.') + parser.add_argument('--total-samples', default=None, type=int, help='Stops fetching after this many samples and keeps the chart open until its window is closed; omit to run until the chart window is closed or Ctrl+C is pressed.') parser.add_argument('-op', '--option-string', default='', type=str, help='Sets the initial value of certain attributes for the session.') args = parser.parse_args(argsv) - example(args.resource_name, args.option_string, args.function, args.range, args.digits, args.trigger_source, args.fetch_count) + example(args.resource_name, args.option_string, args.function, args.range, args.digits, args.trigger_source, args.total_samples) def main(): @@ -73,13 +99,13 @@ def test_example(): plt = pytest.importorskip('matplotlib.pyplot') plt.switch_backend('Agg') # Avoid opening a chart window during the test options = {'simulate': True, 'driver_setup': {'Model': '4082', 'BoardType': 'PXIe', }, } - example('PXI1Slot2', options, 'DC_VOLTS', 10, 5.5, 'IMMEDIATE', 2) + example('PXI1Slot2', options, 'DC_VOLTS', 10, 5.5, 'IMMEDIATE', 10) def test_main(): plt = pytest.importorskip('matplotlib.pyplot') plt.switch_backend('Agg') # Avoid opening a chart window during the test - cmd_line = ['--option-string', 'Simulate=1, DriverSetup=Model:4082; BoardType:PXIe', '--trigger-source', 'IMMEDIATE', '--fetch-count', '2', ] + cmd_line = ['--option-string', 'Simulate=1, DriverSetup=Model:4082; BoardType:PXIe', '--trigger-source', 'IMMEDIATE', '--total-samples', '10', ] _main(cmd_line) From ae5f4bdddf36e73f33534448add2a7aa9eace380 Mon Sep 17 00:00:00 2001 From: "Selvam, Divakar" Date: Wed, 21 Oct 2026 17:01:24 +0530 Subject: [PATCH 3/4] Trigger CI build From 44a6e25e23c181f5019edef4002bdd4b313534eb Mon Sep 17 00:00:00 2001 From: NI-velmurugan Date: Mon, 26 Oct 2026 13:32:39 +0000 Subject: [PATCH 4/4] Resolving PR comments --- ...idmm_cont_acq_multiple_samples_ext_trig.py | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/nidmm/examples/nidmm_cont_acq_multiple_samples_ext_trig.py b/src/nidmm/examples/nidmm_cont_acq_multiple_samples_ext_trig.py index d25042eae..9d868338e 100644 --- a/src/nidmm/examples/nidmm_cont_acq_multiple_samples_ext_trig.py +++ b/src/nidmm/examples/nidmm_cont_acq_multiple_samples_ext_trig.py @@ -24,7 +24,6 @@ def _create_chart(plt): def _update_chart(plt, axes, line, measurements, new_samples, samples_fetched): measurements.extend(new_samples) samples_fetched += len(new_samples) - # The x-axis keeps absolute sample numbers, so it starts after the samples dropped from the history first_sample = samples_fetched - len(measurements) line.set_data(range(first_sample, samples_fetched), measurements) axes.relim() @@ -34,19 +33,20 @@ def _update_chart(plt, axes, line, measurements, new_samples, samples_fetched): def example(resource_name, options, function, measurement_range, digits, trigger_source, total_samples): - # Imported lazily because matplotlib is unavailable on some platforms (e.g. 32-bit Python) import matplotlib.pyplot as plt - with nidmm.Session(resource_name=resource_name, options=options) as session: + with nidmm.Session(resource_name=resource_name, reset_device=True, options=options) as session: session.configure_measurement_digits(measurement_function=nidmm.Function[function], range=measurement_range, resolution_digits=digits) - # Trigger and sample trigger slope are not configured: the slope properties and enums are not in the Python API (#875) - session.configure_trigger(trigger_source=nidmm.TriggerSource[trigger_source]) - # The sample trigger uses the same source as the trigger; a sample_count of 0 configures a continuous acquisition - session.configure_multi_point(trigger_count=1, sample_count=0, sample_trigger=nidmm.SampleTrigger[trigger_source]) + # Trigger slopes use driver defaults because the public API has no slope configuration (#875). + try: + session.configure_trigger(trigger_source=nidmm.TriggerSource[trigger_source]) + # The sample trigger uses the same source as the trigger; a sample_count of 0 configures a continuous acquisition + session.configure_multi_point(trigger_count=1, sample_count=0, sample_trigger=nidmm.SampleTrigger[trigger_source]) + except nidmm.errors.DriverError as e: + # Not every device supports every trigger source (for example, NI 408x devices have no external trigger) + sys.exit('The device does not support trigger source {}.\n{}'.format(trigger_source, e.description)) - # Create an empty chart; the line is filled in as samples arrive figure, axes, line = _create_chart(plt) - # Only the most recent CHART_HISTORY_SIZE measurements are kept measurements = collections.deque(maxlen=CHART_HISTORY_SIZE) samples_fetched = 0 @@ -57,17 +57,15 @@ def example(resource_name, options, function, measurement_range, digits, trigger total_samples is None or samples_fetched < total_samples ): backlog, _ = session.read_status() + if total_samples is not None: + # Prevent the last fetch from exceeding the requested total. + backlog = min(backlog, total_samples - samples_fetched) if backlog > 0: - # The last fetch only takes the samples still needed to reach total_samples - samples_in_this_fetch = backlog - if total_samples is not None: - samples_in_this_fetch = min(backlog, total_samples - samples_fetched) - - new_samples = session.fetch_multi_point(array_size=samples_in_this_fetch) - - # Add the new samples to the chart and get the updated total sample count + new_samples = session.fetch_multi_point(array_size=backlog) samples_fetched = _update_chart(plt, axes, line, measurements, new_samples, samples_fetched) - plt.pause(0.001) + else: + # Process chart events while waiting for the next trigger. + plt.pause(0.001) # The acquisition has stopped; keep the final chart on screen until its window is closed if total_samples is not None and plt.fignum_exists(figure.number):