From 699885ebcf6449cd286e0758570e9717f5d84aed Mon Sep 17 00:00:00 2001 From: Ankit Singh Date: Tue, 10 Mar 2026 23:48:27 +0530 Subject: [PATCH] added Mixed measuerements --- src/nipcbatt/pcbatt_library/dmm/__init__.py | 56 +++-- .../dmm/mixed_measurements/__init__.py | 1 + .../mixed_measurements/mixed_measurement.py | 181 ++++++++++++++ .../mixed_measurement_constants.py | 59 +++++ .../mixed_measurement_data_types.py | 227 ++++++++++++++++++ .../custom_mixed_measurement.py | 49 ++++ .../default_mixed_measurement.py | 30 +++ .../mixed_measurement/__init__.py | 1 + .../test_integration_dmm_mixed_measurement.py | 198 +++++++++++++++ .../mixed_measurements/__init__.py | 1 + .../test_mixed_measurement.py | 59 +++++ 11 files changed, 843 insertions(+), 19 deletions(-) create mode 100644 src/nipcbatt/pcbatt_library/dmm/mixed_measurements/__init__.py create mode 100644 src/nipcbatt/pcbatt_library/dmm/mixed_measurements/mixed_measurement.py create mode 100644 src/nipcbatt/pcbatt_library/dmm/mixed_measurements/mixed_measurement_constants.py create mode 100644 src/nipcbatt/pcbatt_library/dmm/mixed_measurements/mixed_measurement_data_types.py create mode 100644 src/nipcbatt/pcbatt_validation_examples/DMM_examples/mixed_measurement/custom_mixed_measurement.py create mode 100644 src/nipcbatt/pcbatt_validation_examples/DMM_examples/mixed_measurement/default_mixed_measurement.py create mode 100644 tests/nipcbatt_tests/pcbatt_library_integration_tests/mixed_measurement/__init__.py create mode 100644 tests/nipcbatt_tests/pcbatt_library_integration_tests/mixed_measurement/test_integration_dmm_mixed_measurement.py create mode 100644 tests/nipcbatt_tests/pcbatt_library_tests/mixed_measurements/__init__.py create mode 100644 tests/nipcbatt_tests/pcbatt_library_tests/mixed_measurements/test_mixed_measurement.py diff --git a/src/nipcbatt/pcbatt_library/dmm/__init__.py b/src/nipcbatt/pcbatt_library/dmm/__init__.py index dae2f3d..04977a7 100644 --- a/src/nipcbatt/pcbatt_library/dmm/__init__.py +++ b/src/nipcbatt/pcbatt_library/dmm/__init__.py @@ -15,6 +15,24 @@ FormatMeasurement, RangeAndMeasurementFunctionParameters, ) +from nipcbatt.pcbatt_library.dmm.dc_rms_current_measurements.dc_rms_current_constants import ( + DEFAULT_DC_RMS_CURRENT_AC_MIN_FREQUENCY, + DEFAULT_DC_RMS_CURRENT_EXECUTION_TYPE, + DEFAULT_DC_RMS_CURRENT_MEASUREMENT_CONFIGURATION, + DEFAULT_DC_RMS_CURRENT_MEASUREMENT_PARAMETERS, + DEFAULT_DC_RMS_CURRENT_TIMING_PARAMETERS, + DEFAULT_DC_RMS_CURRENT_TRIGGER_PARAMETERS, + ConstantsForDcRmsCurrentMeasurements, +) +from nipcbatt.pcbatt_library.dmm.dc_rms_current_measurements.dc_rms_current_data_types import ( + CurrentRangeAndFunctions, + DcRmsCurrentMeasurementConfiguration, + DcRmsCurrentMeasurementFunctionParameters, + DcRmsCurrentMeasurementResultData, +) +from nipcbatt.pcbatt_library.dmm.dc_rms_current_measurements.dc_rms_current_measurement import ( + DcRmsCurrentMeasurement, +) from nipcbatt.pcbatt_library.dmm.dc_rms_voltage_measurements.dc_rms_voltage_constants import ( DEFAULT_DC_RMS_VOLTAGE_AC_MIN_FREQUENCY, DEFAULT_DC_RMS_VOLTAGE_EXECUTION_TYPE, @@ -33,6 +51,24 @@ from nipcbatt.pcbatt_library.dmm.dc_rms_voltage_measurements.dc_rms_voltage_measurement import ( DcRmsVoltageMeasurement, ) +from nipcbatt.pcbatt_library.dmm.mixed_measurements.mixed_measurement import ( + MixedMeasurement, +) +from nipcbatt.pcbatt_library.dmm.mixed_measurements.mixed_measurement_constants import ( + DEFAULT_MIXED_AC_MIN_FREQUENCY, + DEFAULT_MIXED_EXECUTION_TYPE, + DEFAULT_MIXED_MEASUREMENT_CONFIGURATION, + DEFAULT_MIXED_MEASUREMENT_PARAMETERS, + DEFAULT_MIXED_TIMING_PARAMETERS, + DEFAULT_MIXED_TRIGGER_PARAMETERS, + ConstantsForMixedMeasurements, +) +from nipcbatt.pcbatt_library.dmm.mixed_measurements.mixed_measurement_data_types import ( + MixedMeasurementConfiguration, + MixedMeasurementFunctionParameters, + MixedMeasurementResultData, + MixedRangeAndFunctions, +) from nipcbatt.pcbatt_library.dmm.resistance_measurements.resistance_constants import ( DEFAULT_RESISTANCE_AC_MIN_FREQUENCY, DEFAULT_RESISTANCE_EXECUTION_TYPE, @@ -49,23 +85,5 @@ ResistanceRangeAndFunctions, ) from nipcbatt.pcbatt_library.dmm.resistance_measurements.resistance_measurement import ( - DcRmsResistanceMeasurement) - -from nipcbatt.pcbatt_library.dmm.dc_rms_current_measurements.dc_rms_current_constants import ( - DEFAULT_DC_RMS_CURRENT_AC_MIN_FREQUENCY, - DEFAULT_DC_RMS_CURRENT_EXECUTION_TYPE, - DEFAULT_DC_RMS_CURRENT_MEASUREMENT_CONFIGURATION, - DEFAULT_DC_RMS_CURRENT_MEASUREMENT_PARAMETERS, - DEFAULT_DC_RMS_CURRENT_TIMING_PARAMETERS, - DEFAULT_DC_RMS_CURRENT_TRIGGER_PARAMETERS, - ConstantsForDcRmsCurrentMeasurements, -) -from nipcbatt.pcbatt_library.dmm.dc_rms_current_measurements.dc_rms_current_data_types import ( - CurrentRangeAndFunctions, - DcRmsCurrentMeasurementConfiguration, - DcRmsCurrentMeasurementFunctionParameters, - DcRmsCurrentMeasurementResultData, -) -from nipcbatt.pcbatt_library.dmm.dc_rms_current_measurements.dc_rms_current_measurement import ( - DcRmsCurrentMeasurement, + DcRmsResistanceMeasurement, ) diff --git a/src/nipcbatt/pcbatt_library/dmm/mixed_measurements/__init__.py b/src/nipcbatt/pcbatt_library/dmm/mixed_measurements/__init__.py new file mode 100644 index 0000000..39128f3 --- /dev/null +++ b/src/nipcbatt/pcbatt_library/dmm/mixed_measurements/__init__.py @@ -0,0 +1 @@ +"""Provides Mixed measurement functionality using DMM""" \ No newline at end of file diff --git a/src/nipcbatt/pcbatt_library/dmm/mixed_measurements/mixed_measurement.py b/src/nipcbatt/pcbatt_library/dmm/mixed_measurements/mixed_measurement.py new file mode 100644 index 0000000..56ea230 --- /dev/null +++ b/src/nipcbatt/pcbatt_library/dmm/mixed_measurements/mixed_measurement.py @@ -0,0 +1,181 @@ +"""Defines class used for mixed measurement on PCB points.""" + +from typing import Union + +import nidmm + +from nipcbatt.pcbatt_library.common.common_data_types import MeasurementExecutionType +from nipcbatt.pcbatt_library.dmm.common.common_data_types import ( + TimingParameters, + TriggerParameters, +) +from nipcbatt.pcbatt_library.dmm.common.helper_functions import ( + FormatMeasurement, + RangeAndMeasurementFunctionParameters, +) +from nipcbatt.pcbatt_library.dmm.mixed_measurements.mixed_measurement_data_types import ( + MixedMeasurementConfiguration, + MixedMeasurementFunctionParameters, + MixedMeasurementResultData, +) +from nipcbatt.pcbatt_library_core.daq.pcbatt_building_blocks import ( + BuildingBlockUsingNIDMM, +) + + +class MixedMeasurement(BuildingBlockUsingNIDMM): + """Defines a way that allows you to perform mixed measurements on PCB points.""" + + def initialize(self, dmm_resource_name: str, powerline_frequency: float): + """Initializes the DMM session with the specific resource name and powerline frequency. + + Args: + dmm_resource_name (str): + The resource name of the DMM device to use for measurements. + powerline_frequency (float): + The powerline frequency in Hz. + """ + self._dmm_resource_name = dmm_resource_name + self._powerline_frequency = powerline_frequency + + self._instrument = nidmm.Session(resource_name=self._dmm_resource_name) + self.session.powerline_freq = self._powerline_frequency + + def configure_and_measure( + self, configuration: MixedMeasurementConfiguration + ) -> Union[MixedMeasurementResultData, None]: + """Configures and/or performs a measurement according to specific configuration parameters. + + Args: + configuration (MixedMeasurementConfiguration): + An instance of `MixedMeasurementConfiguration` used to configure + the measurement. + + Returns: + MixedMeasurementResultData | None: An instance of + `MixedMeasurementResultData` containing DMM execution settings + and the measured value, or None if only configuration was performed. + """ + if configuration.execution_type in ( + MeasurementExecutionType.CONFIGURE_ONLY, + MeasurementExecutionType.CONFIGURE_AND_MEASURE, + ): + self.configure_measurement_function( + parameters=configuration.measurement_function_parameters + ) + self.configure_trigger(parameters=configuration.trigger_parameters) + self.configure_timing(parameters=configuration.timing_parameters) + if self.session.function == nidmm.Function.AC_VOLTS: + self.session.ac_min_freq = configuration.ac_min_frequency + + if configuration.execution_type in ( + MeasurementExecutionType.MEASURE_ONLY, + MeasurementExecutionType.CONFIGURE_AND_MEASURE, + ): + # dmm_read = self.session.read() + return self.acquire_measurement( + configuration.measurement_function_parameters.resolution_in_digits.value + ) + return None + + def close(self): + """Closes measurement procedure and releases internal resources.""" + if self.is_session_initialized: + self.session.close() + self._instrument = None + + def configure_measurement_function(self, parameters: MixedMeasurementFunctionParameters): + """Configures the measurement function settings for the DMM. + + Args: + parameters (MixedMeasurementFunctionParameters): + An instance of `MixedMeasurementFunctionParameters` containing the + measurement function type and resolution in digits to configure. + """ + measurement_function_and_range = RangeAndMeasurementFunctionParameters( + parameters.measurement_function + ) + resolution_in_digits = parameters.resolution_in_digits.value + self.session.configure_measurement_digits( + measurement_function=measurement_function_and_range.measurement_function, + range=measurement_function_and_range.range_value, + resolution_digits=resolution_in_digits, + ) + + def configure_trigger(self, parameters: TriggerParameters): + """Configure the characteristics of triggers used for mixed measurements. + + Args: + parameters (TriggerParameters): + An instance of `TriggerParameters` containing trigger source, + trigger delay, slope, and enable/disable flag. + """ + if not parameters.enable_trigger: + self.session.configure_trigger( + trigger_source=nidmm.TriggerSource.IMMEDIATE, trigger_delay=-1.0 + ) + return + self.session.configure_trigger( + trigger_source=nidmm.TriggerSource[parameters.trigger_source.name], + trigger_delay=parameters.trigger_delay, + ) + # Configure Slope if trigger is enabled + nidmm_trigger_slope_attribute_id = 1250334 + self.session._set_attribute_vi_int32( + nidmm_trigger_slope_attribute_id, parameters.trigger_slope.value + ) + + def configure_timing(self, parameters: TimingParameters): + """Configures the timing characteristics used for mixed measurements. + + Args: + parameters (TimingParameters): + An instance of `TimingParameters` containing aperture time and + settle time settings. + """ + self.session.aperture_time = parameters.aperture_time_seconds + self.session.settle_time = parameters.settle_time_seconds + + def acquire_measurement(self, range_in_digits: float) -> MixedMeasurementResultData: + """Acquires and formats the measurement result data. + + Args: + range_in_digits (float): + The resolution in digits used for formatting the measured value. + + Returns: + MixedMeasurementResultData: + An instance of `MixedMeasurementResultData` containing: + - dmm_execution_settings: Dictionary with keys 'Function', 'Range', + 'Digits_Resolution', 'Aperture_Time(s)', 'Settle_Time(s)', + 'Minimum_Frequency(Hz)', 'Absolute_Resolution', + 'Input_Resistance(Ohm)', and 'Auto_Range_Value' + - measurement: Dictionary with keys 'Measured_Value', 'Unit', and + 'Formatted_Measurement' + """ + measured_value = self.session.read() + measurement = FormatMeasurement.measurement( + range_in_digits=range_in_digits, + measured_value=measured_value, + measurement_function=self.session.function, + ) + aperture_time = "{}{}".format( + *FormatMeasurement.format_with_si_prefix(self.session.aperture_time, 3) + ) + settle_time = "{}{}".format( + *FormatMeasurement.format_with_si_prefix(self.session.settle_time.total_seconds(), 3) + ) + dmm_execution_settings = { + "Function": self.session.function.name, + "Range": self.session.range, + "Digits_Resolution": self.session.resolution_digits, + "Aperture_Time(s)": aperture_time, + "Settle_Time(s)": settle_time, + "Minimum_Frequency(Hz)": self.session.ac_min_freq, + "Absolute_Resolution": self.session.resolution_absolute, + "Input_Resistance(Ohm)": self.session.input_resistance, + "Auto_Range_Value": self.session.auto_range_value, + } + return MixedMeasurementResultData( + dmm_execution_settings=dmm_execution_settings, measurement=measurement + ) diff --git a/src/nipcbatt/pcbatt_library/dmm/mixed_measurements/mixed_measurement_constants.py b/src/nipcbatt/pcbatt_library/dmm/mixed_measurements/mixed_measurement_constants.py new file mode 100644 index 0000000..6c70a6b --- /dev/null +++ b/src/nipcbatt/pcbatt_library/dmm/mixed_measurements/mixed_measurement_constants.py @@ -0,0 +1,59 @@ +"""Constants data types for Mixed Measurements.""" + +import dataclasses + +from nipcbatt.pcbatt_library.dmm.common.common_data_types import ( + TimingParameters, + TriggerParameters, +) +from nipcbatt.pcbatt_library.dmm.common.constants import ConstantsForDcRmsMeasurements +from nipcbatt.pcbatt_library.dmm.mixed_measurements.mixed_measurement_data_types import ( + MixedMeasurementConfiguration, + MixedMeasurementFunctionParameters, + MixedRangeAndFunctions, +) + + +@dataclasses.dataclass +class ConstantsForMixedMeasurements: + """Constants used for Mixed measurement.""" + + RANGE_AND_FUNCTION = MixedRangeAndFunctions.DC_Voltage_Auto_Range + + +# Mixed measurement-specific range/function setting is defined +# in ConstantsForMixedMeasurements. +# Default execution type for Mixed measurements +DEFAULT_MIXED_EXECUTION_TYPE = ConstantsForDcRmsMeasurements.DEFAULT_EXECUTION_TYPE + +# Default measurement function parameter including Mixed range/function and resolution in digits +DEFAULT_MIXED_MEASUREMENT_PARAMETERS = MixedMeasurementFunctionParameters( + measurement_function=ConstantsForMixedMeasurements.RANGE_AND_FUNCTION, + resolution_in_digits=ConstantsForDcRmsMeasurements.DEFAULT_RESOLUTION_IN_DIGITS, +) + +# Default timing parameters including aperture time and settle time +DEFAULT_MIXED_TIMING_PARAMETERS = TimingParameters( + aperture_time_seconds=ConstantsForDcRmsMeasurements.DEFAULT_APERTURE_TIME_SECONDS, + settle_time_seconds=ConstantsForDcRmsMeasurements.DEFAULT_SETTLE_TIME_SECONDS, +) + +# Default AC minimum frequency +DEFAULT_MIXED_AC_MIN_FREQUENCY = ConstantsForDcRmsMeasurements.DEFAULT_AC_MIN_FREQUENCY + +# Default trigger parameters including trigger source, trigger delay, and trigger enable setting +DEFAULT_MIXED_TRIGGER_PARAMETERS = TriggerParameters( + trigger_source=ConstantsForDcRmsMeasurements.DEFAULT_TRIGGER_SOURCE, + trigger_delay=ConstantsForDcRmsMeasurements.DEFAULT_TRIGGER_DELAY, + slope=ConstantsForDcRmsMeasurements.DEFAULT_TRIGGER_SLOPE, + enable_trigger=ConstantsForDcRmsMeasurements.DEFAULT_ENABLE_TRIGGER, +) + +# Default DC-RMS Mixed measurement configuration +DEFAULT_MIXED_MEASUREMENT_CONFIGURATION = MixedMeasurementConfiguration( + execution_type=DEFAULT_MIXED_EXECUTION_TYPE, + measurement_function_parameters=DEFAULT_MIXED_MEASUREMENT_PARAMETERS, + trigger_parameters=DEFAULT_MIXED_TRIGGER_PARAMETERS, + timing_parameters=DEFAULT_MIXED_TIMING_PARAMETERS, + ac_min_frequency=DEFAULT_MIXED_AC_MIN_FREQUENCY, +) diff --git a/src/nipcbatt/pcbatt_library/dmm/mixed_measurements/mixed_measurement_data_types.py b/src/nipcbatt/pcbatt_library/dmm/mixed_measurements/mixed_measurement_data_types.py new file mode 100644 index 0000000..678ccd2 --- /dev/null +++ b/src/nipcbatt/pcbatt_library/dmm/mixed_measurements/mixed_measurement_data_types.py @@ -0,0 +1,227 @@ +"""Mixed measurement data types.""" + +from enum import Enum +from typing import Union + +import nidmm + +from nipcbatt.pcbatt_library.common.common_data_types import MeasurementExecutionType +from nipcbatt.pcbatt_library.dmm.common.common_data_types import ( + ResolutionInDigits, + TimingParameters, + TriggerParameters, +) +from nipcbatt.pcbatt_library_core.pcbatt_data_types import PCBATestToolkitData + + +class MixedRangeAndFunctions(Enum): + """Defines the measurement function and range settings for mixed measurement.""" + + DC_Voltage_Auto_Range = (nidmm.Function.DC_VOLTS, -1.0) + DC_100mV = (nidmm.Function.DC_VOLTS, 0.1) + DC_1V = (nidmm.Function.DC_VOLTS, 1) + DC_10V = (nidmm.Function.DC_VOLTS, 10) + DC_100V = (nidmm.Function.DC_VOLTS, 100) + DC_300V = (nidmm.Function.DC_VOLTS, 300) + + AC_Voltage_Auto_Range = (nidmm.Function.AC_VOLTS, -1.0) + AC_50mV = (nidmm.Function.AC_VOLTS, 0.05) + AC_200mV = (nidmm.Function.AC_VOLTS, 0.2) + AC_500mV = (nidmm.Function.AC_VOLTS, 0.5) + AC_2V = (nidmm.Function.AC_VOLTS, 2) + AC_5V = (nidmm.Function.AC_VOLTS, 5) + AC_20V = (nidmm.Function.AC_VOLTS, 20) + AC_50V = (nidmm.Function.AC_VOLTS, 50) + AC_300V = (nidmm.Function.AC_VOLTS, 300) + + DC_Current_Auto_Range = (nidmm.Function.DC_CURRENT, -1.0) + DC_1uA = (nidmm.Function.DC_CURRENT, 0.000001) + DC_10uA = (nidmm.Function.DC_CURRENT, 0.00001) + DC_100uA = (nidmm.Function.DC_CURRENT, 0.0001) + DC_1mA = (nidmm.Function.DC_CURRENT, 0.001) + DC_10mA = (nidmm.Function.DC_CURRENT, 0.01) + DC_20mA = (nidmm.Function.DC_CURRENT, 0.02) + DC_100mA = (nidmm.Function.DC_CURRENT, 0.1) + DC_200mA = (nidmm.Function.DC_CURRENT, 0.2) + DC_1A = (nidmm.Function.DC_CURRENT, 1) + DC_3A = (nidmm.Function.DC_CURRENT, 3) + + AC_Current_Auto_Range = (nidmm.Function.AC_CURRENT, -1.0) + AC_100uA = (nidmm.Function.AC_CURRENT, 0.0001) + AC_1mA = (nidmm.Function.AC_CURRENT, 0.001) + AC_10mA = (nidmm.Function.AC_CURRENT, 0.01) + AC_100mA = (nidmm.Function.AC_CURRENT, 0.1) + AC_500mA = (nidmm.Function.AC_CURRENT, 0.5) + AC_1A = (nidmm.Function.AC_CURRENT, 1) + AC_3A = (nidmm.Function.AC_CURRENT, 3) + + TWO_W_Resistance_Auto_Range = (nidmm.Function.TWO_WIRE_RES, -1.0) + TWO_W_RES_100_Ohm = (nidmm.Function.TWO_WIRE_RES, 100) + TWO_W_RES_1k_Ohm = (nidmm.Function.TWO_WIRE_RES, 1000) + TWO_W_RES_10k_Ohm = (nidmm.Function.TWO_WIRE_RES, 10000) + TWO_W_RES_100k_Ohm = (nidmm.Function.TWO_WIRE_RES, 100000) + TWO_W_RES_1M_Ohm = (nidmm.Function.TWO_WIRE_RES, 1000000) + TWO_W_RES_10M_Ohm = (nidmm.Function.TWO_WIRE_RES, 10000000) + TWO_W_RES_100M_Ohm = (nidmm.Function.TWO_WIRE_RES, 100000000) + TWO_W_RES_5G_Ohm = (nidmm.Function.TWO_WIRE_RES, 5000000000) + + FOUR_W_Resistance_Auto_Range = (nidmm.Function.FOUR_WIRE_RES, -1.0) + FOUR_W_RES_100_Ohm = (nidmm.Function.FOUR_WIRE_RES, 100) + FOUR_W_RES_1k_Ohm = (nidmm.Function.FOUR_WIRE_RES, 1000) + FOUR_W_RES_10k_Ohm = (nidmm.Function.FOUR_WIRE_RES, 10000) + FOUR_W_RES_100k_Ohm = (nidmm.Function.FOUR_WIRE_RES, 100000) + FOUR_W_RES_1M_Ohm = (nidmm.Function.FOUR_WIRE_RES, 1000000) + FOUR_W_RES_10M_Ohm = (nidmm.Function.FOUR_WIRE_RES, 10000000) + + +class MixedMeasurementFunctionParameters: + """Defines parameters used for configuration of mixed measurement.""" + + def __init__( + self, + measurement_function: MixedRangeAndFunctions, + resolution_in_digits: ResolutionInDigits, + ) -> None: + """Initializes measurement function parameters. + + Args: + measurement_function (MixedRangeAndFunctions): + The mixed measurement function and range setting. + resolution_in_digits (ResolutionInDigits): + The measurement resolution in digits. + """ + self._measurement_function = measurement_function + self._resolution_in_digits = resolution_in_digits + + @property + def measurement_function(self) -> MixedRangeAndFunctions: + """Gets the mixed measurement function and range setting. + + Returns: + MixedRangeAndFunctions: The configured mixed range and function. + """ + return self._measurement_function + + @property + def resolution_in_digits(self) -> ResolutionInDigits: + """Gets the measurement resolution in digits. + + Returns: + ResolutionInDigits: The configured resolution setting. + """ + return self._resolution_in_digits + + +class MixedMeasurementConfiguration(PCBATestToolkitData): + """Defines configuration parameters for mixed measurements.""" + + def __init__( + self, + execution_type: MeasurementExecutionType, + measurement_function_parameters: MixedMeasurementFunctionParameters, + trigger_parameters: TriggerParameters, + timing_parameters: TimingParameters, + ac_min_frequency: float, + ) -> None: + """Initializes the mixed measurement configuration. + + Args: + execution_type (MeasurementExecutionType): + Specifies whether to configure only, measure only, or both configure and measure. + measurement_function_parameters (MixedMeasurementFunctionParameters): + The measurement function settings including mixed range and resolution. + trigger_parameters (TriggerParameters): + Trigger configuration including source, delay, and enable/disable settings. + timing_parameters (TimingParameters): + Timing settings including aperture time and settle time. + ac_min_frequency (float): + Minimum frequency for AC Voltage, Current measurements in Hz + (ignored for DC measurements). + """ + self._execution_type = execution_type + self._measurement_function_parameters = measurement_function_parameters + self._trigger_parameters = trigger_parameters + self._timing_parameters = timing_parameters + self._ac_min_frequency = ac_min_frequency + + @property + def execution_type(self) -> MeasurementExecutionType: + """Gets the measurement execution type. + + Returns: + MeasurementExecutionType: The execution mode (configure only, measure only, or both). + """ + return self._execution_type + + @property + def trigger_parameters(self) -> TriggerParameters: + """Gets the trigger configuration parameters. + + Returns: + TriggerParameters: The trigger settings for the measurement. + """ + return self._trigger_parameters + + @property + def measurement_function_parameters(self) -> MixedMeasurementFunctionParameters: + """Gets the measurement function parameters. + + Returns: + MixedMeasurementFunctionParameters: The mixed range, function, + and resolution settings. + """ + return self._measurement_function_parameters + + @property + def timing_parameters(self) -> TimingParameters: + """Gets the timing configuration parameters. + + Returns: + TimingParameters: The aperture time and settle time settings. + """ + return self._timing_parameters + + @property + def ac_min_frequency(self) -> float: + """Gets the minimum AC frequency setting. + + Returns: + float: The minimum frequency for AC voltage, current measurements. + """ + return self._ac_min_frequency + + +class MixedMeasurementResultData(PCBATestToolkitData): + """Defines mixed measurement results obtained from DMM mixed measurement.""" + + def __init__(self, dmm_execution_settings: dict, measurement: Union[dict, None]) -> None: + """Initializes the mixed measurement result data. + + Args: + dmm_execution_settings (dict): + Dictionary containing the DMM configuration used during measurement. + measurement (dict | None): + Dictionary containing the measurement results, + or None if only configuration was performed. + """ + self._dmm_execution_settings = dmm_execution_settings + self._measurement = measurement + + @property + def dmm_execution_settings(self) -> dict: + """Gets the DMM execution settings used during the measurement. + + Returns: + dict: Dictionary with labeled keys and values including units for each setting. + """ + return self._dmm_execution_settings + + @property + def measurement(self) -> Union[dict, None]: + """Gets the measurement result data. + + Returns: + dict | None: Dictionary containing 'Measured_Value', 'Unit', and + 'Formatted_Measurement' keys, or None if only configuration was performed. + """ + return self._measurement diff --git a/src/nipcbatt/pcbatt_validation_examples/DMM_examples/mixed_measurement/custom_mixed_measurement.py b/src/nipcbatt/pcbatt_validation_examples/DMM_examples/mixed_measurement/custom_mixed_measurement.py new file mode 100644 index 0000000..47a0ed4 --- /dev/null +++ b/src/nipcbatt/pcbatt_validation_examples/DMM_examples/mixed_measurement/custom_mixed_measurement.py @@ -0,0 +1,49 @@ +"""DMM Mixed mmeasurement example with custom input parameters.""" + +import nidmm + +import nipcbatt +from nipcbatt.pcbatt_library import dmm +from nipcbatt.pcbatt_utilities.pcbatt_logger import PcbattLogger + + +def main(): + """Configures and executes custom DMM mixed measurement with logging.""" + dmm_mixed_measurement = dmm.MixedMeasurement() + + logger = PcbattLogger(file="c:\\Temp\\mixed_measurement_logger.txt") + logger.attach(dmm_mixed_measurement) + + config = dmm.MixedMeasurementConfiguration( + execution_type=nipcbatt.MeasurementExecutionType.CONFIGURE_AND_MEASURE, + trigger_parameters=dmm.TriggerParameters( + trigger_source=nidmm.TriggerSource.IMMEDIATE, + trigger_delay=2.0, + slope=dmm.Slope.RISING_EDGE, + enable_trigger=False, + ), + measurement_function_parameters=dmm.MixedMeasurementFunctionParameters( + measurement_function=dmm.MixedRangeAndFunctions.AC_Current_Auto_Range, + resolution_in_digits=dmm.ResolutionInDigits.DIGITS_5_5, + ), + timing_parameters=dmm.TimingParameters( + aperture_time_seconds=-1.0, + settle_time_seconds=-1.0, + ), + ac_min_frequency=40.0, + ) + # ======================= Initialize the DMM ============================ + dmm_mixed_measurement.initialize("Sim_DMM", 50) + + # ================= Default measurement configuration =================== + measurement = dmm_mixed_measurement.configure_and_measure(configuration=config) + + # ===================== Close the DMM session =========================== + dmm_mixed_measurement.close() + + # Print the measurement result + print(measurement.dmm_execution_settings, measurement.measurement) + + +if __name__ == "__main__": + main() diff --git a/src/nipcbatt/pcbatt_validation_examples/DMM_examples/mixed_measurement/default_mixed_measurement.py b/src/nipcbatt/pcbatt_validation_examples/DMM_examples/mixed_measurement/default_mixed_measurement.py new file mode 100644 index 0000000..4106ac6 --- /dev/null +++ b/src/nipcbatt/pcbatt_validation_examples/DMM_examples/mixed_measurement/default_mixed_measurement.py @@ -0,0 +1,30 @@ +"""Mixed mmeasurement example with default input parameters.""" + +from nipcbatt import dmm +from nipcbatt.pcbatt_utilities.pcbatt_logger import PcbattLogger + + +def main(): + """Configures and executes default Mixed measurement with logging.""" + dmm_mixed_measurement = dmm.MixedMeasurement() + + logger = PcbattLogger(file="c:\\Temp\\mixed_measurement_logger.txt") + logger.attach(dmm_mixed_measurement) + + # ======================= Initialize the DMM ============================ + dmm_mixed_measurement.initialize("Sim_DMM", 50) + + # ================= Default measurement configuration =================== + measurement = dmm_mixed_measurement.configure_and_measure( + configuration=dmm.DEFAULT_MIXED_MEASUREMENT_CONFIGURATION + ) + + # ===================== Close the DMM session =========================== + dmm_mixed_measurement.close() + + # Print the measurement result + print(measurement.dmm_execution_settings, measurement.measurement) + + +if __name__ == "__main__": + main() diff --git a/tests/nipcbatt_tests/pcbatt_library_integration_tests/mixed_measurement/__init__.py b/tests/nipcbatt_tests/pcbatt_library_integration_tests/mixed_measurement/__init__.py new file mode 100644 index 0000000..dde9ae7 --- /dev/null +++ b/tests/nipcbatt_tests/pcbatt_library_integration_tests/mixed_measurement/__init__.py @@ -0,0 +1 @@ +"""Provides a set of integration tests for nipcbatt.pcbatt_library.mixed_measurements package""" \ No newline at end of file diff --git a/tests/nipcbatt_tests/pcbatt_library_integration_tests/mixed_measurement/test_integration_dmm_mixed_measurement.py b/tests/nipcbatt_tests/pcbatt_library_integration_tests/mixed_measurement/test_integration_dmm_mixed_measurement.py new file mode 100644 index 0000000..85029cc --- /dev/null +++ b/tests/nipcbatt_tests/pcbatt_library_integration_tests/mixed_measurement/test_integration_dmm_mixed_measurement.py @@ -0,0 +1,198 @@ +"""This module provides integration tests for DMM mixed measurement.""" + +import importlib.metadata +import logging +import sys +import unittest + +import nidmm +import nipcbatt +from nipcbatt import dmm + + +class TestIntegrationMixedMeasurement(unittest.TestCase): + """Defines integration checks for `dmm.MixedMeasurement`.""" + + RESOURCE_NAME = "Sim_DMM" + POWERLINE_FREQUENCY = 60.0 + + def setUp(self): + pass + + def tearDown(self): + pass + + @classmethod + def setUpClass(cls): + print("Setup test fixture") + logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) + logging.debug("python version = %s", str(sys.version)) + logging.debug("python path = %s", sys.executable) + + try: + used_nidmm_version = importlib.metadata.version("nidmm") + except importlib.metadata.PackageNotFoundError: + used_nidmm_version = "not installed" + logging.debug("used_nidmm_version = %s", used_nidmm_version) + + @classmethod + def tearDownClass(cls): + print("Teardown fixture") + + def _create_initialized_uut(self) -> dmm.MixedMeasurement: + uut = dmm.MixedMeasurement() + try: + uut.initialize(self.RESOURCE_NAME, self.POWERLINE_FREQUENCY) + except nidmm.errors.DriverError as error: + self.skipTest(f"NI-DMM resource '{self.RESOURCE_NAME}' not available: {error}") + return uut + + def test_initialize_configure_measure_close_dc_voltage(self): + """Happy path: DC voltage mixed measurement configure and measure.""" + uut = self._create_initialized_uut() + + cfg = dmm.MixedMeasurementConfiguration( + execution_type=nipcbatt.MeasurementExecutionType.CONFIGURE_AND_MEASURE, + measurement_function_parameters=dmm.MixedMeasurementFunctionParameters( + dmm.MixedRangeAndFunctions.DC_10V, + dmm.ResolutionInDigits.DIGITS_5_5, + ), + trigger_parameters=dmm.TriggerParameters( + trigger_source=nidmm.TriggerSource.IMMEDIATE, + trigger_delay=0.0, + slope=dmm.Slope.RISING_EDGE, + enable_trigger=True, + ), + timing_parameters=dmm.TimingParameters(0.001, 0.01), + ac_min_frequency=10.0, + ) + + try: + result = uut.configure_and_measure(cfg) + + self.assertIsNotNone(result) + self.assertIsInstance(result, dmm.MixedMeasurementResultData) + self.assertIn("Measured_Value", result.measurement) + finally: + uut.close() + + def test_configure_only_returns_none(self): + """Ensures CONFIGURE_ONLY execution returns None.""" + uut = self._create_initialized_uut() + + cfg = dmm.MixedMeasurementConfiguration( + execution_type=nipcbatt.MeasurementExecutionType.CONFIGURE_ONLY, + measurement_function_parameters=dmm.MixedMeasurementFunctionParameters( + dmm.MixedRangeAndFunctions.TWO_W_RES_1k_Ohm, + dmm.ResolutionInDigits.DIGITS_4_5, + ), + trigger_parameters=dmm.TriggerParameters( + trigger_source=nidmm.TriggerSource.IMMEDIATE, + trigger_delay=0.0, + slope=dmm.Slope.RISING_EDGE, + enable_trigger=False, + ), + timing_parameters=dmm.TimingParameters(0.001, 0.01), + ac_min_frequency=10.0, + ) + + try: + result = uut.configure_and_measure(cfg) + self.assertIsNone(result) + finally: + uut.close() + + def test_measure_only_reads_and_wraps_result(self): + """Ensures MEASURE_ONLY returns mixed measurement result data.""" + uut = self._create_initialized_uut() + + cfg = dmm.MixedMeasurementConfiguration( + execution_type=nipcbatt.MeasurementExecutionType.MEASURE_ONLY, + measurement_function_parameters=dmm.MixedMeasurementFunctionParameters( + dmm.MixedRangeAndFunctions.DC_100mA, + dmm.ResolutionInDigits.DIGITS_5_5, + ), + trigger_parameters=dmm.TriggerParameters( + trigger_source=nidmm.TriggerSource.IMMEDIATE, + trigger_delay=0.0, + slope=dmm.Slope.RISING_EDGE, + enable_trigger=True, + ), + timing_parameters=dmm.TimingParameters(0.001, 0.01), + ac_min_frequency=10.0, + ) + + try: + result = uut.configure_and_measure(cfg) + self.assertIsNotNone(result) + self.assertIsInstance(result.dmm_execution_settings, dict) + self.assertIsInstance(result.measurement, dict) + finally: + uut.close() + + def test_ac_measurement_applies_ac_min_frequency(self): + """AC measurement path should apply configured AC minimum frequency.""" + uut = self._create_initialized_uut() + + cfg = dmm.MixedMeasurementConfiguration( + execution_type=nipcbatt.MeasurementExecutionType.CONFIGURE_AND_MEASURE, + measurement_function_parameters=dmm.MixedMeasurementFunctionParameters( + dmm.MixedRangeAndFunctions.AC_20V, + dmm.ResolutionInDigits.DIGITS_4_5, + ), + trigger_parameters=dmm.TriggerParameters( + trigger_source=nidmm.TriggerSource.IMMEDIATE, + trigger_delay=0.0, + slope=dmm.Slope.RISING_EDGE, + enable_trigger=True, + ), + timing_parameters=dmm.TimingParameters(0.001, 0.01), + ac_min_frequency=55.0, + ) + + try: + result = uut.configure_and_measure(cfg) + self.assertIsNotNone(result) + self.assertEqual(result.dmm_execution_settings["Minimum_Frequency(Hz)"], 55.0) + finally: + uut.close() + + def test_multiple_mixed_ranges(self): + """Verifies mixed measurement with multiple function/range settings.""" + uut = self._create_initialized_uut() + + test_ranges = [ + dmm.MixedRangeAndFunctions.DC_1V, + dmm.MixedRangeAndFunctions.DC_10mA, + dmm.MixedRangeAndFunctions.TWO_W_RES_10k_Ohm, + dmm.MixedRangeAndFunctions.FOUR_W_RES_1k_Ohm, + ] + + try: + for mixed_range in test_ranges: + cfg = dmm.MixedMeasurementConfiguration( + execution_type=nipcbatt.MeasurementExecutionType.CONFIGURE_AND_MEASURE, + measurement_function_parameters=dmm.MixedMeasurementFunctionParameters( + mixed_range, + dmm.ResolutionInDigits.DIGITS_4_5, + ), + trigger_parameters=dmm.TriggerParameters( + trigger_source=nidmm.TriggerSource.IMMEDIATE, + trigger_delay=0.0, + slope=dmm.Slope.RISING_EDGE, + enable_trigger=True, + ), + timing_parameters=dmm.TimingParameters(0.001, 0.01), + ac_min_frequency=10.0, + ) + + result = uut.configure_and_measure(cfg) + self.assertIsNotNone(result) + self.assertIn("Function", result.dmm_execution_settings) + self.assertIn("Measured_Value", result.measurement) + finally: + uut.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/nipcbatt_tests/pcbatt_library_tests/mixed_measurements/__init__.py b/tests/nipcbatt_tests/pcbatt_library_tests/mixed_measurements/__init__.py new file mode 100644 index 0000000..bc2db6d --- /dev/null +++ b/tests/nipcbatt_tests/pcbatt_library_tests/mixed_measurements/__init__.py @@ -0,0 +1 @@ +"""Provides Unit tests for mixed measurements in the pcbatt_library.""" diff --git a/tests/nipcbatt_tests/pcbatt_library_tests/mixed_measurements/test_mixed_measurement.py b/tests/nipcbatt_tests/pcbatt_library_tests/mixed_measurements/test_mixed_measurement.py new file mode 100644 index 0000000..076010d --- /dev/null +++ b/tests/nipcbatt_tests/pcbatt_library_tests/mixed_measurements/test_mixed_measurement.py @@ -0,0 +1,59 @@ +# pylint: disable=C0301 +"""This module provides MixedMeasurement check.""" + +import importlib.metadata +import logging +from pathlib import Path +import sys +import unittest + +# Force using local source tree instead of installed package in venv. +sys.path.insert(0, str(Path(__file__).resolve().parents[4] / "src")) + +import nipcbatt +from nipcbatt import dmm + + +class TestMixedMeasurement(unittest.TestCase): + """Defines a test fixture that checks + `MixedMeasurement` class is ready to use. + + Args: + unittest.TestCase: Base class from which this class inherits. + """ # noqa: D205, D415, W505 - 1 blank line required between summary line and description (auto-generated noqa), First line should end with a period, question mark, or exclamation point (auto-generated noqa), doc line too long (201 > 100 characters) (auto-generated noqa) + + def setUp(self): + pass + + def tearDown(self): + pass + + @classmethod + def setUpClass(cls): + print("Setup test fixture") + logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) + logging.debug("python version = %s", str(sys.version)) + logging.debug("python path = %s", sys.executable) + + used_nidaqmx_version = importlib.metadata.version("nidaqmx") + logging.debug("used_nidaqmx_version = %s", used_nidaqmx_version) + + @classmethod + def tearDownClass(cls): + print("Teardown fixture") + + def test_mixed_measurement(self): + """Checks if class MixedMeasurement is ready to use""" # noqa: D415, W505 - First line should end with a period, question mark, or exclamation point (auto-generated noqa), doc line too long (172 > 100 characters) (auto-generated noqa) + measurement = dmm.MixedMeasurement() + try: + measurement.initialize(dmm_resource_name="Sim_DMM", powerline_frequency=50) + + measurement.configure_and_measure( + configuration=dmm.DEFAULT_MIXED_MEASUREMENT_CONFIGURATION + ) + finally: + measurement.close() + + +if __name__ == "__main__": + unittest.main()