Skip to content

Commit

Permalink
Create visa_dmm custom driver and updated nivisa_dmm_measurement (#439)
Browse files Browse the repository at this point in the history
* Refactored VISA examples to use custom driver

* fixed lint errors

* Fixed black formatting issues

* fixed black issues in helper.py

* Updated the Readme file

* fixed review comments by refactoring visa dmm py

* Fixed Brad's comment on visa dmm driver

* Updated error message

---------

Co-authored-by: Brad Keryan <[email protected]>
  • Loading branch information
Avinash2Suresh and bkeryan authored Oct 13, 2023
1 parent 60ead5b commit 16a5ecb
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 209 deletions.
68 changes: 0 additions & 68 deletions examples/nivisa_dmm_measurement/NIInstrumentSimulatorV2_0.yaml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<PinMap xmlns="http://www.ni.com/TestStand/SemiconductorModule/PinMap.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" schemaVersion="1.1">
<Instruments>
<Instrument name="GPIB0::3::INSTR" instrumentTypeId="DigitalMultimeterSimulator">
<Instrument name="GPIB0::3::INSTR" instrumentTypeId="VisaDmm">
<Channel id="0" />
</Instrument>
</Instruments>
Expand Down
20 changes: 13 additions & 7 deletions examples/nivisa_dmm_measurement/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## NI-VISA DMM Measurement

This is a MeasurementLink example that performs a DMM measurement using NI-VISA
and an NI Instrument Simulator v2.0.
and a DMM that supports SCPI commands.

### Features

Expand All @@ -24,29 +24,35 @@ and an NI Instrument Simulator v2.0.
- NI-VISA
- Optional: NI Instrument Simulator software

Note: there is no Python instrument driver for the NI Instrument Simulator, so
this example directly performs low-level, device-specific commands and queries.
> **Note:**
>
> This example uses the custom instrument driver `_visa_dmm.py` to perform the device-specific commands and queries.
### Required Hardware

By default, this example does not require hardware; it uses PyVISA-sim to
simulate the instrument in software.
[`NIInstrumentSimulatorV2_0.yaml`](./NIInstrumentSimulatorV2_0.yaml) defines the
[`_visa_dmm_sim.yaml`](./_visa_dmm_sim.yaml) defines the
behavior of the simulated instrument.

To use NI Instrument Simulator hardware:
Supported instrument models:
- NI Instrument Simulator v2.0
- HP/Agilent/Keysight 34401A DMM

To use a physical instrument:
- Disable software simulation by setting `USE_SIMULATION = False` in
[`_constants.py`](./_constants.py).
- Connect the NI Instrument Simulator over GPIB or serial.
- Connect the instrument to a supported interface, such as GPIB or serial.
- By default, the pin map included with this example uses the resource name
`GPIB0::3::INSTR`, which matches the NI Instrument Simulator's factory default
settings when connected via GPIB.
- If this doesn't match your configuration, edit
- If this doesn't match your instrument's configuration, edit
[`NIVisaDmmMeasurement.pinmap`](./NIVisaDmmMeasurement.pinmap) and replace
`GPIB0::3::INSTR` with the desired resource name (e.g. `ASRL1::INSTR`).
- To modify the NI Instrument Simulator configuration (e.g. GPIB address,
serial configuration), use the `Instrument Simulator Wizard` included with
the NI Instrument Simulator software.
- To configure third party instruments, see the documentation provided with the instrument.

### Session Management

Expand Down
11 changes: 9 additions & 2 deletions examples/nivisa_dmm_measurement/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ def update_pin_map(self, pin_map_path: str) -> str:
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_path, pin_map_xml=pin_map_path_obj.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)
return response.pin_map_id
Expand Down Expand Up @@ -158,7 +159,13 @@ def resolve_file_path(self, file_path: str) -> str:
"""
if pathlib.Path(file_path).is_absolute():
return file_path
(_, absolute_path, _, _, user_canceled) = self._sequence_context.Engine.FindFileEx(
(
_,
absolute_path,
_,
_,
user_canceled,
) = self._sequence_context.Engine.FindFileEx(
fileToFind=file_path,
absolutePath=None,
srchDirType=None,
Expand Down
136 changes: 136 additions & 0 deletions examples/nivisa_dmm_measurement/_visa_dmm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
"""Custom instrument driver for MeasurementLink NI-VISA DMM examples."""
from __future__ import annotations

import pathlib
import sys
from enum import Enum
from types import TracebackType
from typing import (
TYPE_CHECKING,
Optional,
Type,
)

import pyvisa
import pyvisa.resources
import pyvisa.typing


if TYPE_CHECKING:
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self


# Pin map instrument type constant for VISA DMM
INSTRUMENT_TYPE_VISA_DMM = "VisaDmm"

_SIMULATION_YAML_PATH = pathlib.Path(__file__).resolve().parent / "_visa_dmm_sim.yaml"

_RESOLUTION_DIGITS_TO_VALUE = {"3.5": 0.001, "4.5": 0.0001, "5.5": 1e-5, "6.5": 1e-6}

# Supported NI-VISA DMM instrument IDs, both real and simulated, can be added here
_SUPPORTED_INSTRUMENT_IDS = ["Waveform Generator Simulator", "34401"]


class Function(Enum):
"""Enum that represents the measurement function."""

DC_VOLTS = 0
AC_VOLTS = 1


_FUNCTION_TO_VALUE = {
Function.DC_VOLTS: "VOLT:DC",
Function.AC_VOLTS: "VOLT:AC",
}


class Session:
"""An NI-VISA DMM session."""

def __init__(
self,
resource_name: str,
id_query: bool = True,
reset_device: bool = True,
simulate: bool = False,
) -> None:
"""Open NI-VISA DMM session."""
# Create a real or simulated VISA resource manager."""
visa_library = f"{_SIMULATION_YAML_PATH}@sim" if simulate else ""
resource_manager = pyvisa.ResourceManager(visa_library)

session = resource_manager.open_resource(
resource_name, read_termination="\n", write_termination="\n"
)

if not isinstance(session, pyvisa.resources.MessageBasedResource):
raise TypeError("The 'session' object must be an instance of MessageBasedResource.")
self._session = session

if id_query:
self._validate_id()

if reset_device:
self._reset()

def close(self) -> None:
"""Close the session."""
self._session.close()

def __enter__(self) -> Self:
"""Context management protocol. Returns self."""
return self

def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None:
"""Context management protocol. Calls close()."""
self.close()

def configure_measurement_digits(
self, function: Function, range: float, resolution_digits: float
) -> None:
"""Configure the common properties of the measurement.
These properties include function, range, and resolution_digits.
"""
function_enum = _FUNCTION_TO_VALUE[function]
resolution_value = _RESOLUTION_DIGITS_TO_VALUE[str(resolution_digits)]

self._session.write("CONF:%s %.g,%.g" % (function_enum, range, resolution_value))
self._check_error()

def read(self) -> float:
"""Acquires a single measurement and returns the measured value."""
response = self._session.query("READ?")
self._check_error()
return float(response)

def _check_error(self) -> None:
"""Query the instrument's error queue."""
response = self._session.query("SYST:ERR?")
fields = response.split(",", maxsplit=1)
assert len(fields) >= 1
if int(fields[0]) != 0:
raise RuntimeError("Instrument returned error %s: %s" % (fields[0], fields[1]))

def _validate_id(self) -> None:
"""Check the selected instrument is proper and responding.."""
instrument_id = self._session.query("*IDN?")
if not any(id_check in instrument_id for id_check in _SUPPORTED_INSTRUMENT_IDS):
raise RuntimeError(
"The ID query failed. This may mean that you selected the wrong instrument, your instrument did not respond, "
f"or you are using a model that is not officially supported by this driver. Instrument ID: {instrument_id}"
)

def _reset(self) -> None:
"""Reset the instrument to a known state."""
self._session.write("*CLS")
self._session.write("*RST")
self._check_error()
32 changes: 32 additions & 0 deletions examples/nivisa_dmm_measurement/_visa_dmm_sim.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
spec: "1.1"
devices:
VisaDmm:
eom:
GPIB INSTR:
q: "\n"
r: "\n"
dialogues:
- q: "*CLS"
- q: "*IDN?"
r: "National Instruments,Waveform Generator Simulator (simulated with pyvisa-sim),00000000,2.0.1"
- q: "*RST"
- q: "READ?"
r: "1.23456"
error:
error_queue:
- q: 'SYST:ERR?'
default: '0,No Error'
command_error: '-100,Command Error'
query_error: '-400,Query Error'
properties:
configuration:
default: "VOLT:DC 5.000000,0.001000"
getter:
q: "CONF?"
r: "{:s}"
setter:
q: "CONF:{:s}"

resources:
GPIB0::3::INSTR:
device: VisaDmm
62 changes: 0 additions & 62 deletions examples/nivisa_dmm_measurement/_visa_helpers.py

This file was deleted.

Loading

0 comments on commit 16a5ecb

Please sign in to comment.