Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated the TestStand sequence of measurement examples to make it simpler (DMM, FGEN, SCOPE, SWITCH, VISA DMM) #254

Merged
merged 8 commits into from
May 2, 2023
4 changes: 4 additions & 0 deletions examples/nidcpower_source_dc_voltage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ This is a MeasurementLink example that sources and measures a DC voltage with an
`ui_file_paths` array in `measurement.py`
- Includes a TestStand sequence showing how to configure the pin map, register
instrument sessions with the session management service, and run a measurement
- For the sake of simplicity, the TestStand sequence handles pin map and session
registration and unregistration in the `Setup` and `Cleanup` sections of the main
sequence. For **Test UUTs** and batch process model use cases, these steps should
be moved to the `ProcessSetup` and `ProcessCleanup` callbacks.
- Uses the NI gRPC Device Server to allow sharing instrument sessions with other
measurement services when running measurements from TestStand

Expand Down
6,095 changes: 2,685 additions & 3,410 deletions examples/nidmm_measurement/NIDmmMeasurement.seq

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions examples/nidmm_measurement/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ This is a MeasurementLink example that performs a measurement using an NI DMM.
- Includes InstrumentStudio and MeasurementLink UI Editor project files
- Includes a TestStand sequence showing how to configure the pin map, register
instrument sessions with the session management service, and run a measurement
- For the sake of simplicity, the TestStand sequence handles pin map and session
registration and unregistration in the `Setup` and `Cleanup` sections of the main
sequence. For **Test UUTs** and batch process model use cases, these steps should
be moved to the `ProcessSetup` and `ProcessCleanup` callbacks.
- Uses the NI gRPC Device Server to allow sharing instrument sessions with other
measurement services when running measurements from TestStand

Expand Down
76 changes: 69 additions & 7 deletions examples/nidmm_measurement/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import logging
import pathlib
from typing import Dict, NamedTuple, TypeVar
from typing import Any, Dict, NamedTuple, TypeVar

import grpc

Expand Down Expand Up @@ -48,23 +48,25 @@ def __init__(self, *, grpc_channel: grpc.Channel):
pin_map_service_pb2_grpc.PinMapServiceStub(grpc_channel)
)

def update_pin_map(self, pin_map_id: str) -> None:
def update_pin_map(self, pin_map_path: str) -> str:
"""Update registered pin map contents.

Create and register a pin map if a pin map resource for the specified pin map id is not
found.

Args:
pin_map_id (str): The resource id of the pin map to register as a pin map resource. By
convention, the pin map id is the .pinmap file path.
pin_map_path: The file path of the pin map to register as a pin map resource.

Returns:
The resource id of the pin map that is registered to the pin map service.
"""
pin_map_path = pathlib.Path(pin_map_id)
pin_map_path_obj = pathlib.Path(pin_map_path)
# By convention, the pin map id is the .pinmap file path.
request = pin_map_service_pb2.UpdatePinMapFromXmlRequest(
pin_map_id=pin_map_id, pin_map_xml=pin_map_path.read_text(encoding="utf-8")
pin_map_id=pin_map_path, pin_map_xml=pin_map_path_obj.read_text(encoding="utf-8")
)
response: pin_map_service_pb2.PinMap = self._client.UpdatePinMapFromXml(request)
assert response.pin_map_id == pin_map_id
return response.pin_map_id


class GrpcChannelPoolHelper(GrpcChannelPool):
Expand Down Expand Up @@ -108,3 +110,63 @@ def get_grpc_device_channel(self, provided_interface: str) -> grpc.Channel:
service_class="ni.measurementlink.v1.grpcdeviceserver",
).insecure_address
)


class TestStandSupport(object):
"""Class that communicates with TestStand."""

def __init__(self, sequence_context: Any) -> None:
"""Initialize the TestStandSupport object.

Args:
sequence_context:
The SequenceContext COM object from the TestStand sequence execution.
(Dynamically typed.)
"""
self._sequence_context = sequence_context

def get_active_pin_map_id(self) -> str:
"""Get the active pin map id from the NI.MeasurementLink.PinMapId temporary global variable.

Returns:
The resource id of the pin map that is registered to the pin map service.
"""
return self._sequence_context.Engine.TemporaryGlobals.GetValString(
"NI.MeasurementLink.PinMapId", 0x0
)

def set_active_pin_map_id(self, pin_map_id: str) -> None:
"""Set the NI.MeasurementLink.PinMapId temporary global variable to the specified id.

Args:
pin_map_id:
The resource id of the pin map that is registered to the pin map service.
"""
self._sequence_context.Engine.TemporaryGlobals.SetValString(
"NI.MeasurementLink.PinMapId", 0x1, pin_map_id
)

def resolve_file_path(self, file_path: str) -> str:
"""Resolve the absolute path to a file using the TestStand search directories.

Args:
file_path:
An absolute or relative path to the file. If this is a relative path, this function
searches the TestStand search directories for it.

Returns:
The absolute path to the file.
"""
if pathlib.Path(file_path).is_absolute():
return file_path
(_, absolute_path, _, _, user_canceled) = self._sequence_context.Engine.FindFileEx(
fileToFind=file_path,
absolutePath=None,
srchDirType=None,
searchDirectoryIndex=None,
userCancelled=None, # Must match spelling used by TestStand
searchContext=self._sequence_context.SequenceFile,
)
if user_canceled:
raise RuntimeError("File lookup canceled by user.")
return absolute_path
32 changes: 24 additions & 8 deletions examples/nidmm_measurement/teststand_fixture.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,49 @@
"""Functions to set up and tear down sessions of NI-DMM devices in NI TestStand."""
from typing import Any

import nidmm
from _helpers import GrpcChannelPoolHelper, PinMapClient
from _helpers import GrpcChannelPoolHelper, PinMapClient, TestStandSupport

import ni_measurementlink_service as nims


def update_pin_map(pin_map_id: str) -> None:
def update_pin_map(pin_map_path: str, sequence_context: Any) -> None:
"""Update registered pin map contents.

Create and register a pin map if a pin map resource for the specified pin map id is not found.

Args:
pin_map_id (str): The resource id of the pin map to register as a pin map resource. By
convention, the pin_map_id is the .pinmap file path.

pin_map_path:
An absolute or relative path to the pin map file.
sequence_context:
The SequenceContext COM object from the TestStand sequence execution.
(Dynamically typed.)
"""
teststand_support = TestStandSupport(sequence_context)
pin_map_abs_path = teststand_support.resolve_file_path(pin_map_path)

with GrpcChannelPoolHelper() as grpc_channel_pool:
pin_map_client = PinMapClient(grpc_channel=grpc_channel_pool.pin_map_channel)
pin_map_client.update_pin_map(pin_map_id)
pin_map_id = pin_map_client.update_pin_map(pin_map_abs_path)

teststand_support.set_active_pin_map_id(pin_map_id)


def create_nidmm_sessions(pin_map_id: str) -> None:
"""Create and register all NI-DMM sessions."""
def create_nidmm_sessions(sequence_context: Any) -> None:
"""Create and register all NI-DMM sessions.

Args:
sequence_context:
The SequenceContext COM object from the TestStand sequence execution.
(Dynamically typed.)
"""
with GrpcChannelPoolHelper() as grpc_channel_pool:
session_management_client = nims.session_management.Client(
grpc_channel=grpc_channel_pool.session_management_channel
)

teststand_support = TestStandSupport(sequence_context)
pin_map_id = teststand_support.get_active_pin_map_id()
pin_map_context = nims.session_management.PinMapContext(pin_map_id=pin_map_id, sites=None)
with session_management_client.reserve_sessions(
context=pin_map_context,
Expand Down
Loading