diff --git a/CHANGELOG.md b/CHANGELOG.md index de5e911a8..e378fee00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ All notable changes to this project will be documented in this file. * ### Resolved Issues * Fix PEP 660 builds by setting `build-system` to use `poetry-core` + * [579: nidaqmx does not generate numbered virtual channel names correctly](https://github.com/ni/nidaqmx-python/issues/579) * ### Major Changes * Removed the `docs` extra and converted it to a Poetry dependency group. diff --git a/generated/nidaqmx/_base_interpreter.py b/generated/nidaqmx/_base_interpreter.py index ad7b34584..87c970ec8 100644 --- a/generated/nidaqmx/_base_interpreter.py +++ b/generated/nidaqmx/_base_interpreter.py @@ -1168,6 +1168,10 @@ def get_write_attribute_uint32(self, task, attribute): def get_write_attribute_uint64(self, task, attribute): raise NotImplementedError + @abc.abstractmethod + def internal_get_last_created_chan(self): + raise NotImplementedError + @abc.abstractmethod def is_task_done(self, task): raise NotImplementedError diff --git a/generated/nidaqmx/_grpc_interpreter.py b/generated/nidaqmx/_grpc_interpreter.py index 942de74d5..7091a9e70 100644 --- a/generated/nidaqmx/_grpc_interpreter.py +++ b/generated/nidaqmx/_grpc_interpreter.py @@ -3597,6 +3597,9 @@ def set_runtime_environment( self, environment, environment_version, reserved_1, reserved_2): raise NotImplementedError + def internal_get_last_created_chan(self): + raise NotImplementedError + def _assign_numpy_array(numpy_array, grpc_array): """ diff --git a/generated/nidaqmx/_library_interpreter.py b/generated/nidaqmx/_library_interpreter.py index 6de3d2ca3..1f30052ec 100644 --- a/generated/nidaqmx/_library_interpreter.py +++ b/generated/nidaqmx/_library_interpreter.py @@ -4048,6 +4048,30 @@ def get_write_attribute_uint64(self, task, attribute): self.check_for_error(error_code) return value.value + def internal_get_last_created_chan(self): + cfunc = lib_importer.windll.DAQmxInternalGetLastCreatedChan + if cfunc.argtypes is None: + with cfunc.arglock: + if cfunc.argtypes is None: + cfunc.argtypes = [ + ctypes.c_char_p, ctypes.c_uint] + + temp_size = 0 + while True: + value = ctypes.create_string_buffer(temp_size) + size_or_code = cfunc( + value, temp_size) + if is_string_buffer_too_small(size_or_code): + # Buffer size must have changed between calls; check again. + temp_size = 0 + elif size_or_code > 0 and temp_size == 0: + # Buffer size obtained, use to retrieve data. + temp_size = size_or_code + else: + break + self.check_for_error(size_or_code) + return value.value.decode(lib_importer.encoding) + def is_task_done(self, task): is_task_done = c_bool32() diff --git a/generated/nidaqmx/task/collections/_ai_channel_collection.py b/generated/nidaqmx/task/collections/_ai_channel_collection.py index 998fdcbd4..7ecb49450 100644 --- a/generated/nidaqmx/task/collections/_ai_channel_collection.py +++ b/generated/nidaqmx/task/collections/_ai_channel_collection.py @@ -2,6 +2,7 @@ import numpy +from nidaqmx.errors import DaqFunctionNotSupportedError from nidaqmx.task.channels._ai_channel import AIChannel from nidaqmx.task.collections._channel_collection import ChannelCollection from nidaqmx.utils import unflatten_channel_string @@ -45,18 +46,28 @@ def _create_chan(self, physical_channel, name_to_assign_to_channel=''): Specifies the newly created AIChannel object. """ - if name_to_assign_to_channel: - num_channels = len(unflatten_channel_string(physical_channel)) - - if num_channels > 1: - name = '{}0:{}'.format( - name_to_assign_to_channel, num_channels-1) + # Attempt to retrieve the last created channel name. This is only supported on DAQmx 24Q3+ with the library + # interpreter. + virtual_channel_name = None + try: + virtual_channel_name = self._interpreter.internal_get_last_created_chan() + except (NotImplementedError, DaqFunctionNotSupportedError): + pass + + # Fallback implementation is sometimes incorrect. + if virtual_channel_name is None: + if name_to_assign_to_channel: + num_channels = len(unflatten_channel_string(physical_channel)) + + if num_channels > 1: + virtual_channel_name = '{}0:{}'.format( + name_to_assign_to_channel, num_channels-1) + else: + virtual_channel_name = name_to_assign_to_channel else: - name = name_to_assign_to_channel - else: - name = physical_channel + virtual_channel_name = physical_channel - return AIChannel(self._handle, name, self._interpreter) + return AIChannel(self._handle, virtual_channel_name, self._interpreter) def add_ai_accel_4_wire_dc_voltage_chan( self, physical_channel, name_to_assign_to_channel="", diff --git a/generated/nidaqmx/task/collections/_ao_channel_collection.py b/generated/nidaqmx/task/collections/_ao_channel_collection.py index dbb4508e9..e0c997b27 100644 --- a/generated/nidaqmx/task/collections/_ao_channel_collection.py +++ b/generated/nidaqmx/task/collections/_ao_channel_collection.py @@ -1,5 +1,6 @@ # Do not edit this file; it was automatically generated. +from nidaqmx.errors import DaqFunctionNotSupportedError from nidaqmx.task.channels._ao_channel import AOChannel from nidaqmx.task.collections._channel_collection import ChannelCollection from nidaqmx.utils import unflatten_channel_string @@ -31,18 +32,28 @@ def _create_chan(self, physical_channel, name_to_assign_to_channel=''): Specifies the newly created AOChannel object. """ - if name_to_assign_to_channel: - num_channels = len(unflatten_channel_string(physical_channel)) - - if num_channels > 1: - name = '{}0:{}'.format( - name_to_assign_to_channel, num_channels-1) + # Attempt to retrieve the last created channel name. This is only supported on DAQmx 24Q3+ with the library + # interpreter. + virtual_channel_name = None + try: + virtual_channel_name = self._interpreter.internal_get_last_created_chan() + except (NotImplementedError, DaqFunctionNotSupportedError): + pass + + # Fallback implementation is sometimes incorrect. + if virtual_channel_name is None: + if name_to_assign_to_channel: + num_channels = len(unflatten_channel_string(physical_channel)) + + if num_channels > 1: + virtual_channel_name = '{}0:{}'.format( + name_to_assign_to_channel, num_channels-1) + else: + virtual_channel_name = name_to_assign_to_channel else: - name = name_to_assign_to_channel - else: - name = physical_channel + virtual_channel_name = physical_channel - return AOChannel(self._handle, name, self._interpreter) + return AOChannel(self._handle, virtual_channel_name, self._interpreter) def add_ao_current_chan( self, physical_channel, name_to_assign_to_channel="", min_val=0.0, diff --git a/generated/nidaqmx/task/collections/_ci_channel_collection.py b/generated/nidaqmx/task/collections/_ci_channel_collection.py index c8c1fad60..3ae774927 100644 --- a/generated/nidaqmx/task/collections/_ci_channel_collection.py +++ b/generated/nidaqmx/task/collections/_ci_channel_collection.py @@ -1,5 +1,6 @@ # Do not edit this file; it was automatically generated. +from nidaqmx.errors import DaqFunctionNotSupportedError from nidaqmx.task.channels._ci_channel import CIChannel from nidaqmx.task.collections._channel_collection import ChannelCollection from nidaqmx.utils import unflatten_channel_string @@ -33,18 +34,28 @@ def _create_chan(self, counter, name_to_assign_to_channel=''): Specifies the newly created CIChannel object. """ - if name_to_assign_to_channel: - num_counters = len(unflatten_channel_string(counter)) - - if num_counters > 1: - name = '{}0:{}'.format( - name_to_assign_to_channel, num_counters-1) + # Attempt to retrieve the last created channel name. This is only supported on DAQmx 24Q3+ with the library + # interpreter. + virtual_channel_name = None + try: + virtual_channel_name = self._interpreter.internal_get_last_created_chan() + except (NotImplementedError, DaqFunctionNotSupportedError): + pass + + # Fallback implementation is sometimes incorrect. + if virtual_channel_name is None: + if name_to_assign_to_channel: + num_counters = len(unflatten_channel_string(counter)) + + if num_counters > 1: + virtual_channel_name = '{}0:{}'.format( + name_to_assign_to_channel, num_counters-1) + else: + virtual_channel_name = name_to_assign_to_channel else: - name = name_to_assign_to_channel - else: - name = counter + virtual_channel_name = counter - return CIChannel(self._handle, name, self._interpreter) + return CIChannel(self._handle, virtual_channel_name, self._interpreter) def add_ci_ang_encoder_chan( self, counter, name_to_assign_to_channel="", diff --git a/generated/nidaqmx/task/collections/_co_channel_collection.py b/generated/nidaqmx/task/collections/_co_channel_collection.py index ecd9ed5b4..8922ac9ac 100644 --- a/generated/nidaqmx/task/collections/_co_channel_collection.py +++ b/generated/nidaqmx/task/collections/_co_channel_collection.py @@ -1,5 +1,6 @@ # Do not edit this file; it was automatically generated. +from nidaqmx.errors import DaqFunctionNotSupportedError from nidaqmx.task.channels._co_channel import COChannel from nidaqmx.task.collections._channel_collection import ChannelCollection from nidaqmx.utils import unflatten_channel_string @@ -31,18 +32,28 @@ def _create_chan(self, counter, name_to_assign_to_channel=''): Specifies the newly created COChannel object. """ - if name_to_assign_to_channel: - num_counters = len(unflatten_channel_string(counter)) - - if num_counters > 1: - name = '{}0:{}'.format( - name_to_assign_to_channel, num_counters-1) + # Attempt to retrieve the last created channel name. This is only supported on DAQmx 24Q3+ with the library + # interpreter. + virtual_channel_name = None + try: + virtual_channel_name = self._interpreter.internal_get_last_created_chan() + except (NotImplementedError, DaqFunctionNotSupportedError): + pass + + # Fallback implementation is sometimes incorrect. + if virtual_channel_name is None: + if name_to_assign_to_channel: + num_counters = len(unflatten_channel_string(counter)) + + if num_counters > 1: + virtual_channel_name = '{}0:{}'.format( + name_to_assign_to_channel, num_counters-1) + else: + virtual_channel_name = name_to_assign_to_channel else: - name = name_to_assign_to_channel - else: - name = counter + virtual_channel_name = counter - return COChannel(self._handle, name, self._interpreter) + return COChannel(self._handle, virtual_channel_name, self._interpreter) def add_co_pulse_chan_freq( self, counter, name_to_assign_to_channel="", diff --git a/generated/nidaqmx/task/collections/_di_channel_collection.py b/generated/nidaqmx/task/collections/_di_channel_collection.py index 7c0af1dc8..9704432c4 100644 --- a/generated/nidaqmx/task/collections/_di_channel_collection.py +++ b/generated/nidaqmx/task/collections/_di_channel_collection.py @@ -1,5 +1,6 @@ # Do not edit this file; it was automatically generated. +from nidaqmx.errors import DaqFunctionNotSupportedError from nidaqmx.task.channels._di_channel import DIChannel from nidaqmx.task.collections._channel_collection import ChannelCollection from nidaqmx.utils import unflatten_channel_string @@ -34,25 +35,37 @@ def _create_chan(self, lines, line_grouping, name_to_assign_to_lines=''): Specifies the newly created DIChannel object. """ - unflattened_lines = unflatten_channel_string(lines) - num_lines = len(unflattened_lines) - - if line_grouping == LineGrouping.CHAN_FOR_ALL_LINES: - if name_to_assign_to_lines or num_lines == 1: - name = lines - else: - name = unflattened_lines[0] + '...' - else: - if name_to_assign_to_lines: - if num_lines > 1: - name = '{}0:{}'.format( - name_to_assign_to_lines, num_lines-1) + # Attempt to retrieve the last created channel name. This is only supported on DAQmx 24Q3+ with the library + # interpreter. + virtual_channel_name = None + try: + virtual_channel_name = self._interpreter.internal_get_last_created_chan() + except (NotImplementedError, DaqFunctionNotSupportedError): + pass + + # Fallback implementation is sometimes incorrect. + if virtual_channel_name is None: + unflattened_lines = unflatten_channel_string(lines) + num_lines = len(unflattened_lines) + + if line_grouping == LineGrouping.CHAN_FOR_ALL_LINES: + if name_to_assign_to_lines: + virtual_channel_name = name_to_assign_to_lines + elif num_lines == 1: + virtual_channel_name = lines else: - name = name_to_assign_to_lines + virtual_channel_name = unflattened_lines[0] + '...' else: - name = lines + if name_to_assign_to_lines: + if num_lines > 1: + virtual_channel_name = '{}0:{}'.format( + name_to_assign_to_lines, num_lines-1) + else: + virtual_channel_name = name_to_assign_to_lines + else: + virtual_channel_name = lines - return DIChannel(self._handle, name, self._interpreter) + return DIChannel(self._handle, virtual_channel_name, self._interpreter) def add_di_chan( self, lines, name_to_assign_to_lines="", diff --git a/generated/nidaqmx/task/collections/_do_channel_collection.py b/generated/nidaqmx/task/collections/_do_channel_collection.py index 58e4a26ac..f096a90d2 100644 --- a/generated/nidaqmx/task/collections/_do_channel_collection.py +++ b/generated/nidaqmx/task/collections/_do_channel_collection.py @@ -1,5 +1,6 @@ # Do not edit this file; it was automatically generated. +from nidaqmx.errors import DaqFunctionNotSupportedError from nidaqmx.task.channels._do_channel import DOChannel from nidaqmx.task.collections._channel_collection import ChannelCollection from nidaqmx.utils import unflatten_channel_string @@ -34,25 +35,37 @@ def _create_chan(self, lines, line_grouping, name_to_assign_to_lines=''): Specifies the newly created DOChannel object. """ - unflattened_lines = unflatten_channel_string(lines) - num_lines = len(unflattened_lines) - - if line_grouping == LineGrouping.CHAN_FOR_ALL_LINES: - if name_to_assign_to_lines or num_lines == 1: - name = lines - else: - name = unflattened_lines[0] + '...' - else: - if name_to_assign_to_lines: - if num_lines > 1: - name = '{}0:{}'.format( - name_to_assign_to_lines, num_lines-1) + # Attempt to retrieve the last created channel name. This is only supported on DAQmx 24Q3+ with the library + # interpreter. + virtual_channel_name = None + try: + virtual_channel_name = self._interpreter.internal_get_last_created_chan() + except (NotImplementedError, DaqFunctionNotSupportedError): + pass + + # Fallback implementation is sometimes incorrect. + if virtual_channel_name is None: + unflattened_lines = unflatten_channel_string(lines) + num_lines = len(unflattened_lines) + + if line_grouping == LineGrouping.CHAN_FOR_ALL_LINES: + if name_to_assign_to_lines: + virtual_channel_name = name_to_assign_to_lines + elif num_lines == 1: + virtual_channel_name = lines else: - name = name_to_assign_to_lines + virtual_channel_name = unflattened_lines[0] + '...' else: - name = lines + if name_to_assign_to_lines: + if num_lines > 1: + virtual_channel_name = '{}0:{}'.format( + name_to_assign_to_lines, num_lines-1) + else: + virtual_channel_name = name_to_assign_to_lines + else: + virtual_channel_name = lines - return DOChannel(self._handle, name, self._interpreter) + return DOChannel(self._handle, virtual_channel_name, self._interpreter) def add_do_chan( self, lines, name_to_assign_to_lines="", diff --git a/src/codegen/metadata/functions.py b/src/codegen/metadata/functions.py index da47c3f32..49980a2f6 100644 --- a/src/codegen/metadata/functions.py +++ b/src/codegen/metadata/functions.py @@ -17186,6 +17186,32 @@ 'python_codegen_method': 'CustomCode', 'returns': 'int32' }, + 'InternalGetLastCreatedChan': { + 'calling_convention': 'StdCall', + 'codegen_method': 'private', + 'parameters': [ + { + 'ctypes_data_type': 'ctypes.c_char_p', + 'direction': 'out', + 'name': 'value', + 'python_data_type': 'str', + 'size': { + 'mechanism': 'ivi-dance', + 'value': 'size' + }, + 'type': 'char[]' + }, + { + 'ctypes_data_type': 'ctypes.c_uint32', + 'direction': 'in', + 'name': 'size', + 'python_data_type': 'int', + 'type': 'uInt32' + } + ], + 'python_codegen_method': 'CustomCode', + 'returns': 'int32' + }, 'IsTaskDone': { 'calling_convention': 'StdCall', 'handle_parameter': { diff --git a/src/codegen/templates/_grpc_interpreter.py.mako b/src/codegen/templates/_grpc_interpreter.py.mako index 6d1b4cec2..5a64b136f 100644 --- a/src/codegen/templates/_grpc_interpreter.py.mako +++ b/src/codegen/templates/_grpc_interpreter.py.mako @@ -242,6 +242,9 @@ class GrpcStubInterpreter(BaseInterpreter): self, environment, environment_version, reserved_1, reserved_2): raise NotImplementedError + def internal_get_last_created_chan(self): + raise NotImplementedError + def _assign_numpy_array(numpy_array, grpc_array): """ diff --git a/src/codegen/templates/task/collections/_ai_channel_collection.py.mako b/src/codegen/templates/task/collections/_ai_channel_collection.py.mako index c10f2ce76..fb039a417 100644 --- a/src/codegen/templates/task/collections/_ai_channel_collection.py.mako +++ b/src/codegen/templates/task/collections/_ai_channel_collection.py.mako @@ -8,6 +8,7 @@ import numpy +from nidaqmx.errors import DaqFunctionNotSupportedError from nidaqmx.task.channels._ai_channel import AIChannel from nidaqmx.task.collections._channel_collection import ChannelCollection from nidaqmx.utils import unflatten_channel_string @@ -41,18 +42,28 @@ class AIChannelCollection(ChannelCollection): Specifies the newly created AIChannel object. """ - if name_to_assign_to_channel: - num_channels = len(unflatten_channel_string(physical_channel)) + # Attempt to retrieve the last created channel name. This is only supported on DAQmx 24Q3+ with the library + # interpreter. + virtual_channel_name = None + try: + virtual_channel_name = self._interpreter.internal_get_last_created_chan() + except (NotImplementedError, DaqFunctionNotSupportedError): + pass - if num_channels > 1: - name = '{}0:{}'.format( - name_to_assign_to_channel, num_channels-1) + # Fallback implementation is sometimes incorrect. + if virtual_channel_name is None: + if name_to_assign_to_channel: + num_channels = len(unflatten_channel_string(physical_channel)) + + if num_channels > 1: + virtual_channel_name = '{}0:{}'.format( + name_to_assign_to_channel, num_channels-1) + else: + virtual_channel_name = name_to_assign_to_channel else: - name = name_to_assign_to_channel - else: - name = physical_channel + virtual_channel_name = physical_channel - return AIChannel(self._handle, name, self._interpreter) + return AIChannel(self._handle, virtual_channel_name, self._interpreter) <%namespace name="function_template" file="/function_template.py.mako"/>\ %for function_object in functions: diff --git a/src/codegen/templates/task/collections/_ao_channel_collection.py.mako b/src/codegen/templates/task/collections/_ao_channel_collection.py.mako index 5be2fce8c..640ed8798 100644 --- a/src/codegen/templates/task/collections/_ao_channel_collection.py.mako +++ b/src/codegen/templates/task/collections/_ao_channel_collection.py.mako @@ -6,6 +6,7 @@ %>\ # Do not edit this file; it was automatically generated. +from nidaqmx.errors import DaqFunctionNotSupportedError from nidaqmx.task.channels._ao_channel import AOChannel from nidaqmx.task.collections._channel_collection import ChannelCollection from nidaqmx.utils import unflatten_channel_string @@ -39,18 +40,28 @@ class AOChannelCollection(ChannelCollection): Specifies the newly created AOChannel object. """ - if name_to_assign_to_channel: - num_channels = len(unflatten_channel_string(physical_channel)) + # Attempt to retrieve the last created channel name. This is only supported on DAQmx 24Q3+ with the library + # interpreter. + virtual_channel_name = None + try: + virtual_channel_name = self._interpreter.internal_get_last_created_chan() + except (NotImplementedError, DaqFunctionNotSupportedError): + pass - if num_channels > 1: - name = '{}0:{}'.format( - name_to_assign_to_channel, num_channels-1) + # Fallback implementation is sometimes incorrect. + if virtual_channel_name is None: + if name_to_assign_to_channel: + num_channels = len(unflatten_channel_string(physical_channel)) + + if num_channels > 1: + virtual_channel_name = '{}0:{}'.format( + name_to_assign_to_channel, num_channels-1) + else: + virtual_channel_name = name_to_assign_to_channel else: - name = name_to_assign_to_channel - else: - name = physical_channel + virtual_channel_name = physical_channel - return AOChannel(self._handle, name, self._interpreter) + return AOChannel(self._handle, virtual_channel_name, self._interpreter) <%namespace name="function_template" file="/function_template.py.mako"/>\ %for function_object in functions: diff --git a/src/codegen/templates/task/collections/_ci_channel_collection.py.mako b/src/codegen/templates/task/collections/_ci_channel_collection.py.mako index 7f4fabe55..a0ae77fed 100644 --- a/src/codegen/templates/task/collections/_ci_channel_collection.py.mako +++ b/src/codegen/templates/task/collections/_ci_channel_collection.py.mako @@ -6,6 +6,7 @@ %>\ # Do not edit this file; it was automatically generated. +from nidaqmx.errors import DaqFunctionNotSupportedError from nidaqmx.task.channels._ci_channel import CIChannel from nidaqmx.task.collections._channel_collection import ChannelCollection from nidaqmx.utils import unflatten_channel_string @@ -39,18 +40,28 @@ class CIChannelCollection(ChannelCollection): Specifies the newly created CIChannel object. """ - if name_to_assign_to_channel: - num_counters = len(unflatten_channel_string(counter)) + # Attempt to retrieve the last created channel name. This is only supported on DAQmx 24Q3+ with the library + # interpreter. + virtual_channel_name = None + try: + virtual_channel_name = self._interpreter.internal_get_last_created_chan() + except (NotImplementedError, DaqFunctionNotSupportedError): + pass - if num_counters > 1: - name = '{}0:{}'.format( - name_to_assign_to_channel, num_counters-1) + # Fallback implementation is sometimes incorrect. + if virtual_channel_name is None: + if name_to_assign_to_channel: + num_counters = len(unflatten_channel_string(counter)) + + if num_counters > 1: + virtual_channel_name = '{}0:{}'.format( + name_to_assign_to_channel, num_counters-1) + else: + virtual_channel_name = name_to_assign_to_channel else: - name = name_to_assign_to_channel - else: - name = counter + virtual_channel_name = counter - return CIChannel(self._handle, name, self._interpreter) + return CIChannel(self._handle, virtual_channel_name, self._interpreter) <%namespace name="function_template" file="/function_template.py.mako"/>\ %for function_object in functions: diff --git a/src/codegen/templates/task/collections/_co_channel_collection.py.mako b/src/codegen/templates/task/collections/_co_channel_collection.py.mako index 4fd83d282..a0e36da36 100644 --- a/src/codegen/templates/task/collections/_co_channel_collection.py.mako +++ b/src/codegen/templates/task/collections/_co_channel_collection.py.mako @@ -6,6 +6,7 @@ %>\ # Do not edit this file; it was automatically generated. +from nidaqmx.errors import DaqFunctionNotSupportedError from nidaqmx.task.channels._co_channel import COChannel from nidaqmx.task.collections._channel_collection import ChannelCollection from nidaqmx.utils import unflatten_channel_string @@ -39,18 +40,28 @@ class COChannelCollection(ChannelCollection): Specifies the newly created COChannel object. """ - if name_to_assign_to_channel: - num_counters = len(unflatten_channel_string(counter)) + # Attempt to retrieve the last created channel name. This is only supported on DAQmx 24Q3+ with the library + # interpreter. + virtual_channel_name = None + try: + virtual_channel_name = self._interpreter.internal_get_last_created_chan() + except (NotImplementedError, DaqFunctionNotSupportedError): + pass - if num_counters > 1: - name = '{}0:{}'.format( - name_to_assign_to_channel, num_counters-1) + # Fallback implementation is sometimes incorrect. + if virtual_channel_name is None: + if name_to_assign_to_channel: + num_counters = len(unflatten_channel_string(counter)) + + if num_counters > 1: + virtual_channel_name = '{}0:{}'.format( + name_to_assign_to_channel, num_counters-1) + else: + virtual_channel_name = name_to_assign_to_channel else: - name = name_to_assign_to_channel - else: - name = counter + virtual_channel_name = counter - return COChannel(self._handle, name, self._interpreter) + return COChannel(self._handle, virtual_channel_name, self._interpreter) <%namespace name="function_template" file="/function_template.py.mako"/>\ %for function_object in functions: diff --git a/src/codegen/templates/task/collections/_di_channel_collection.py.mako b/src/codegen/templates/task/collections/_di_channel_collection.py.mako index e80c2aefc..3486716fe 100644 --- a/src/codegen/templates/task/collections/_di_channel_collection.py.mako +++ b/src/codegen/templates/task/collections/_di_channel_collection.py.mako @@ -6,6 +6,7 @@ %>\ # Do not edit this file; it was automatically generated. +from nidaqmx.errors import DaqFunctionNotSupportedError from nidaqmx.task.channels._di_channel import DIChannel from nidaqmx.task.collections._channel_collection import ChannelCollection from nidaqmx.utils import unflatten_channel_string @@ -42,25 +43,37 @@ class DIChannelCollection(ChannelCollection): Specifies the newly created DIChannel object. """ - unflattened_lines = unflatten_channel_string(lines) - num_lines = len(unflattened_lines) - - if line_grouping == LineGrouping.CHAN_FOR_ALL_LINES: - if name_to_assign_to_lines or num_lines == 1: - name = lines - else: - name = unflattened_lines[0] + '...' - else: - if name_to_assign_to_lines: - if num_lines > 1: - name = '{}0:{}'.format( - name_to_assign_to_lines, num_lines-1) + # Attempt to retrieve the last created channel name. This is only supported on DAQmx 24Q3+ with the library + # interpreter. + virtual_channel_name = None + try: + virtual_channel_name = self._interpreter.internal_get_last_created_chan() + except (NotImplementedError, DaqFunctionNotSupportedError): + pass + + # Fallback implementation is sometimes incorrect. + if virtual_channel_name is None: + unflattened_lines = unflatten_channel_string(lines) + num_lines = len(unflattened_lines) + + if line_grouping == LineGrouping.CHAN_FOR_ALL_LINES: + if name_to_assign_to_lines: + virtual_channel_name = name_to_assign_to_lines + elif num_lines == 1: + virtual_channel_name = lines else: - name = name_to_assign_to_lines + virtual_channel_name = unflattened_lines[0] + '...' else: - name = lines + if name_to_assign_to_lines: + if num_lines > 1: + virtual_channel_name = '{}0:{}'.format( + name_to_assign_to_lines, num_lines-1) + else: + virtual_channel_name = name_to_assign_to_lines + else: + virtual_channel_name = lines - return DIChannel(self._handle, name, self._interpreter) + return DIChannel(self._handle, virtual_channel_name, self._interpreter) <%namespace name="function_template" file="/function_template.py.mako"/>\ %for function_object in functions: diff --git a/src/codegen/templates/task/collections/_do_channel_collection.py.mako b/src/codegen/templates/task/collections/_do_channel_collection.py.mako index 0ad283de4..782a16572 100644 --- a/src/codegen/templates/task/collections/_do_channel_collection.py.mako +++ b/src/codegen/templates/task/collections/_do_channel_collection.py.mako @@ -6,6 +6,7 @@ %>\ # Do not edit this file; it was automatically generated. +from nidaqmx.errors import DaqFunctionNotSupportedError from nidaqmx.task.channels._do_channel import DOChannel from nidaqmx.task.collections._channel_collection import ChannelCollection from nidaqmx.utils import unflatten_channel_string @@ -42,25 +43,37 @@ class DOChannelCollection(ChannelCollection): Specifies the newly created DOChannel object. """ - unflattened_lines = unflatten_channel_string(lines) - num_lines = len(unflattened_lines) - - if line_grouping == LineGrouping.CHAN_FOR_ALL_LINES: - if name_to_assign_to_lines or num_lines == 1: - name = lines - else: - name = unflattened_lines[0] + '...' - else: - if name_to_assign_to_lines: - if num_lines > 1: - name = '{}0:{}'.format( - name_to_assign_to_lines, num_lines-1) + # Attempt to retrieve the last created channel name. This is only supported on DAQmx 24Q3+ with the library + # interpreter. + virtual_channel_name = None + try: + virtual_channel_name = self._interpreter.internal_get_last_created_chan() + except (NotImplementedError, DaqFunctionNotSupportedError): + pass + + # Fallback implementation is sometimes incorrect. + if virtual_channel_name is None: + unflattened_lines = unflatten_channel_string(lines) + num_lines = len(unflattened_lines) + + if line_grouping == LineGrouping.CHAN_FOR_ALL_LINES: + if name_to_assign_to_lines: + virtual_channel_name = name_to_assign_to_lines + elif num_lines == 1: + virtual_channel_name = lines else: - name = name_to_assign_to_lines + virtual_channel_name = unflattened_lines[0] + '...' else: - name = lines + if name_to_assign_to_lines: + if num_lines > 1: + virtual_channel_name = '{}0:{}'.format( + name_to_assign_to_lines, num_lines-1) + else: + virtual_channel_name = name_to_assign_to_lines + else: + virtual_channel_name = lines - return DOChannel(self._handle, name, self._interpreter) + return DOChannel(self._handle, virtual_channel_name, self._interpreter) <%namespace name="function_template" file="/function_template.py.mako"/>\ %for function_object in functions: diff --git a/src/codegen/utilities/interpreter_helpers.py b/src/codegen/utilities/interpreter_helpers.py index c0f4b1ae6..a47c8436f 100644 --- a/src/codegen/utilities/interpreter_helpers.py +++ b/src/codegen/utilities/interpreter_helpers.py @@ -68,6 +68,7 @@ "get_error_string", "read_id_pin_memory", "set_runtime_environment", + "internal_get_last_created_chan", ] LIBRARY_INTERPRETER_IGNORED_FUNCTIONS = [ diff --git a/tests/component/task/channels/test_ai_channel.py b/tests/component/task/channels/test_ai_channel.py index 6f4a3d1f8..8ac3c3f2b 100644 --- a/tests/component/task/channels/test_ai_channel.py +++ b/tests/component/task/channels/test_ai_channel.py @@ -34,8 +34,9 @@ ) from nidaqmx.error_codes import DAQmxErrors from nidaqmx.errors import DaqError -from nidaqmx.system import Device +from nidaqmx.system import Device, System from nidaqmx.task.channels import AIChannel +from nidaqmx.utils import unflatten_channel_string from tests.helpers import configure_teds @@ -1294,3 +1295,22 @@ def test___task___add_teds_ai_voltage_chan_with_excit___raises_teds_sensor_not_d ) assert exc_info.value.error_code == DAQmxErrors.TEDS_SENSOR_NOT_DETECTED + + +# For more extensive virtual channel name testing, refer to test_di_channel.py +@pytest.mark.skipif( + System.local().driver_version < (24, 5, 0), + reason="The fix for this test requires DAQmx 24.5.0 and later", +) +@pytest.mark.grpc_xfail( + reason="The fix for this test isn't supported on gRPC", +) +def test___task___add_ai_chans_with_name___sets_channel_name( + task: Task, + sim_6363_device: Device, +) -> None: + chan: AIChannel = task.ai_channels.add_ai_voltage_chan( + f"{sim_6363_device.name}/ai0:3", name_to_assign_to_channel="myChan09" + ) + + assert unflatten_channel_string(chan.name) == unflatten_channel_string("myChan09:12") diff --git a/tests/component/task/channels/test_ci_channel.py b/tests/component/task/channels/test_ci_channel.py index c4923723f..ac16c72d3 100644 --- a/tests/component/task/channels/test_ci_channel.py +++ b/tests/component/task/channels/test_ci_channel.py @@ -383,3 +383,15 @@ def test___task___add_ci_two_edge_sep_chan___sets_channel_attributes( assert chan.ci_meas_type == UsageTypeCI.PULSE_WIDTH_DIGITAL_TWO_EDGE_SEPARATION assert chan.ci_two_edge_sep_first_edge == first_edge assert chan.ci_two_edge_sep_second_edge == second_edge + + +# For more extensive virtual channel name testing, refer to test_di_channel.py +def test___task___add_ci_chans_with_name___sets_channel_name( + task: Task, + sim_6363_device: Device, +) -> None: + chan: CIChannel = task.ci_channels.add_ci_count_edges_chan( + sim_6363_device.ci_physical_chans[0].name, name_to_assign_to_channel="myChan" + ) + + assert chan.name == "myChan" diff --git a/tests/component/task/channels/test_co_channel.py b/tests/component/task/channels/test_co_channel.py index d2239bc58..7ed31a500 100644 --- a/tests/component/task/channels/test_co_channel.py +++ b/tests/component/task/channels/test_co_channel.py @@ -98,3 +98,15 @@ def test___task___add_co_pulse_chan_time___sets_channel_attributes( assert chan.co_pulse_time_initial_delay == initial_delay assert chan.co_pulse_low_time == low_time assert chan.co_pulse_high_time == high_time + + +# For more extensive virtual channel name testing, refer to test_di_channel.py +def test___task___add_co_chans_with_name___sets_channel_name( + task: Task, + sim_6363_device: Device, +) -> None: + chan: COChannel = task.co_channels.add_co_pulse_chan_freq( + sim_6363_device.co_physical_chans[0].name, name_to_assign_to_channel="myChan" + ) + + assert chan.name == "myChan" diff --git a/tests/component/task/channels/test_di_channel.py b/tests/component/task/channels/test_di_channel.py index e12d71c0a..0b7352489 100644 --- a/tests/component/task/channels/test_di_channel.py +++ b/tests/component/task/channels/test_di_channel.py @@ -2,9 +2,9 @@ from nidaqmx import Task from nidaqmx.constants import ChannelType, LineGrouping -from nidaqmx.system import Device +from nidaqmx.system import Device, System from nidaqmx.task.channels import DIChannel -from nidaqmx.utils import flatten_channel_string +from nidaqmx.utils import flatten_channel_string, unflatten_channel_string @pytest.mark.parametrize( @@ -44,3 +44,183 @@ def test___task___add_di_chan_chan_per_line___sets_channel_attributes( for chan in chans: assert chan.chan_type == ChannelType.DIGITAL_INPUT assert chan.di_num_lines == 1 + + +def _test___task___add_di_chans_with_name___sets_channel_name( + task: Task, + sim_6363_device: Device, + phys_chan_list: list[str], + line_grouping: LineGrouping, + name_to_assign_to_lines: str, + expected_virtual_channel_name: str, + qualify_expected_virtual_channel_name: bool, +) -> None: + qualified_physical_channel_name = flatten_channel_string( + [f"{sim_6363_device.name}/{chan}" for chan in phys_chan_list] + ) + chan: DIChannel = task.di_channels.add_di_chan( + qualified_physical_channel_name, + line_grouping=line_grouping, + name_to_assign_to_lines=name_to_assign_to_lines, + ) + + # If a user doesn't specify a virtual channel name, DAQmx will use the physical channel name, + # which we now need to fully quality. + if qualify_expected_virtual_channel_name: + expected_virtual_channel_name = f"{sim_6363_device.name}/{expected_virtual_channel_name}" + + assert unflatten_channel_string(chan.name) == unflatten_channel_string( + expected_virtual_channel_name + ) + + +@pytest.mark.parametrize( + "phys_chan_list, line_grouping, name_to_assign_to_lines, expected_virtual_channel_name, qualify_expected_virtual_channel_name", + [ + (["port0/line0"], LineGrouping.CHAN_PER_LINE, "", "port0/line0", True), + (["port0/line0"], LineGrouping.CHAN_FOR_ALL_LINES, "", "port0/line0", True), + ( + ["port0/line0", "port0/line1"], + LineGrouping.CHAN_PER_LINE, + "", + "port0/line0:1", + True, + ), + ( + ["port0/line0", "port0/line1"], + LineGrouping.CHAN_FOR_ALL_LINES, + "", + "port0/line0...", + True, + ), + (["port0/line0"], LineGrouping.CHAN_PER_LINE, "myChan", "myChan", False), + (["port0/line0"], LineGrouping.CHAN_FOR_ALL_LINES, "myChan", "myChan", False), + (["port0/line0:7"], LineGrouping.CHAN_PER_LINE, "myChan", "myChan0:7", False), + (["port0/line0:7"], LineGrouping.CHAN_FOR_ALL_LINES, "myChan", "myChan", False), + (["port0/line0:7"], LineGrouping.CHAN_FOR_ALL_LINES, "myChan0", "myChan0", False), + ], +) +def test___task___add_di_chans_with_simple_name___sets_channel_name( + task: Task, + sim_6363_device: Device, + phys_chan_list: list[str], + line_grouping: LineGrouping, + name_to_assign_to_lines: str, + expected_virtual_channel_name: str, + qualify_expected_virtual_channel_name: bool, +) -> None: + _test___task___add_di_chans_with_name___sets_channel_name( + task, + sim_6363_device, + phys_chan_list, + line_grouping, + name_to_assign_to_lines, + expected_virtual_channel_name, + qualify_expected_virtual_channel_name, + ) + + +@pytest.mark.skipif( + System.local().driver_version < (24, 5, 0), + reason="The fix for this test requires DAQmx 24.5.0 and later", +) +@pytest.mark.grpc_xfail( + reason="The fix for this test isn't supported on gRPC", +) +@pytest.mark.parametrize( + "phys_chan_list, line_grouping, name_to_assign_to_lines, expected_virtual_channel_name, qualify_expected_virtual_channel_name", + [ + (["port0/line0"], LineGrouping.CHAN_PER_LINE, " ", "port0/line0", True), + (["port0/line0"], LineGrouping.CHAN_FOR_ALL_LINES, " ", "port0/line0", True), + (["port0/line0"], LineGrouping.CHAN_PER_LINE, " myChan ", "myChan", False), + (["port0/line0"], LineGrouping.CHAN_FOR_ALL_LINES, " myChan ", "myChan", False), + (["port0/line0:7"], LineGrouping.CHAN_PER_LINE, "myChan0", "myChan0:7", False), + (["port0/line0:7"], LineGrouping.CHAN_PER_LINE, " myChan0 ", "myChan0:7", False), + (["port0/line0:7"], LineGrouping.CHAN_FOR_ALL_LINES, " myChan0 ", "myChan0", False), + (["port0/line0:7"], LineGrouping.CHAN_PER_LINE, " myChan 0 ", "myChan0:7", False), + (["port0/line0:7"], LineGrouping.CHAN_FOR_ALL_LINES, " myChan 0 ", "myChan 0", False), + (["port0/line0:7"], LineGrouping.CHAN_PER_LINE, "myChan8", "myChan8:15", False), + (["port0/line0:7"], LineGrouping.CHAN_PER_LINE, "myChan008", "myChan008:015", False), + ( + ["port0/line0:1"], + LineGrouping.CHAN_PER_LINE, + "myFirstChan,mySecondChan", + "myFirstChan, mySecondChan", + False, + ), + ( + ["port0/line0:1"], + LineGrouping.CHAN_PER_LINE, + " myFirstChan , mySecondChan ", + "myFirstChan, mySecondChan", + False, + ), + ( + ["port0/line0:1"], + LineGrouping.CHAN_PER_LINE, + "myFirstChan, , mySecondChan", + "myFirstChan, mySecondChan", + False, + ), + ( + ["port0/line0:7"], + LineGrouping.CHAN_PER_LINE, + "myFirstChan,mySecondChan", + "myFirstChan, mySecondChan0:6", + False, + ), + ( + ["port0/line0:7"], + LineGrouping.CHAN_PER_LINE, + "myFirstChan2:5,mySecondChan34", + "myFirstChan2:5, mySecondChan34:37", + False, + ), + ( + ["port0/line0:7"], + LineGrouping.CHAN_PER_LINE, + "myFirstChan0:9", + "myFirstChan0:7", + False, + ), + ( + ["port0/line0:7"], + LineGrouping.CHAN_FOR_ALL_LINES, + "myFirstChan0:9", + "myFirstChan0", + False, + ), + ( + ["port0/line0:7"], + LineGrouping.CHAN_PER_LINE, + "myFirstChan0:6, mySecondChan, myThirdChan", + "myFirstChan0:6, mySecondChan", + False, + ), + ( + ["port0/line0:7"], + LineGrouping.CHAN_PER_LINE, + "myFirstChan0:5, mySecondChan0:5", + "myFirstChan0:5, mySecondChan0:1", + False, + ), + ], +) +def test___task___add_di_chans_with_complex_name___sets_channel_name( + task: Task, + sim_6363_device: Device, + phys_chan_list: list[str], + line_grouping: LineGrouping, + name_to_assign_to_lines: str, + expected_virtual_channel_name: str, + qualify_expected_virtual_channel_name: bool, +) -> None: + _test___task___add_di_chans_with_name___sets_channel_name( + task, + sim_6363_device, + phys_chan_list, + line_grouping, + name_to_assign_to_lines, + expected_virtual_channel_name, + qualify_expected_virtual_channel_name, + ) diff --git a/tests/component/task/channels/test_do_channel.py b/tests/component/task/channels/test_do_channel.py index f9fc1249a..3698c9ce1 100644 --- a/tests/component/task/channels/test_do_channel.py +++ b/tests/component/task/channels/test_do_channel.py @@ -2,9 +2,9 @@ from nidaqmx import Task from nidaqmx.constants import ChannelType, LineGrouping -from nidaqmx.system import Device +from nidaqmx.system import Device, System from nidaqmx.task.channels import DOChannel -from nidaqmx.utils import flatten_channel_string +from nidaqmx.utils import flatten_channel_string, unflatten_channel_string @pytest.mark.parametrize( @@ -44,3 +44,24 @@ def test___task___add_do_chan_chan_per_line___sets_channel_attributes( for chan in chans: assert chan.chan_type == ChannelType.DIGITAL_OUTPUT assert chan.do_num_lines == 1 + + +# For more extensive virtual channel name testing, refer to test_di_channel.py +@pytest.mark.skipif( + System.local().driver_version < (24, 5, 0), + reason="The fix for this test requires DAQmx 24.5.0 and later", +) +@pytest.mark.library_only( + reason="The fix for this test isn't supported on gRPC", +) +def test___task___add_do_chans_with_name___sets_channel_name( + task: Task, + sim_6363_device: Device, +) -> None: + chan: DOChannel = task.do_channels.add_do_chan( + f"{sim_6363_device.name}/port0/line0:7", + line_grouping=LineGrouping.CHAN_PER_LINE, + name_to_assign_to_lines="myChan0", + ) + + assert unflatten_channel_string(chan.name) == unflatten_channel_string("myChan0:7")