Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ PyVISA-py Changelog
0.9.0 (unreleased)
------------------

- VXI-11 and HiSLIP: add support for remote/local #627 PR #636
- A VXI-11 read stopped by both the END indicator and the termination
character now reports ``VI_SUCCESS`` rather than
``VI_SUCCESS_TERM_CHAR``. VPP-4.3 RULE 6.1.1 gives END priority over the
Expand Down
26 changes: 26 additions & 0 deletions docs/source/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,32 @@ omit the argument.
wait on a lock". Set ``lock_timeout`` on the session for that, as in the
DM3068 example above.

Remote/Local control
--------------------

Setting an instrument to Remote or Local is possible via VXI-11, HiSLIP and GPIB.

In PyVISA, this is done through ``inst.control_ren(pyvisa.constants.RENLineOperation.{op})``
where valid ``{op}`` values are:

================ =========== =========================================
RENLineOperation VXI-11 HiSLIP
================ =========== =========================================
address_gtl goto local goto local, no change to remote enable
asrt error enable remote
asrt_address goto remote enable remote, goto remote
asrt_address_llo goto remote enable remote, goto remote, local lockout
asrt_llo error enable remote, local lockout
deassert error disable remote
deassert_gtl goto local disable remote, goto local
================ =========== =========================================

This is fully conform to what VPP-4.3 Rule 6.5.6 and Observations 6.5.1 + 6.5.2 say,
and what NI-VISA does, so this should be fully portable.

GPIB has functionality comparable to HiSLIP, but may behave differently than NI-VISA,
depending on the type of interface.


.. _PySerial: https://pythonhosted.org/pyserial/
.. _PyVISA: http://pyvisa.readthedocs.org/
Expand Down
83 changes: 82 additions & 1 deletion pyvisa_py/tcpip.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import threading
import time
import warnings
from typing import Any, Dict, List, Optional, Tuple, Type, cast
from typing import Any, Dict, Final, List, Optional, Tuple, Type, cast

from pyvisa import attributes, constants, errors, rname
from pyvisa.constants import BufferOperation, ResourceAttribute, StatusCode
Expand Down Expand Up @@ -112,6 +112,16 @@ class TCPIPInstrHiSLIP(Session):
# need to define session_type to make the set_attribute machinery work.
session_type = (constants.InterfaceType.tcpip, "INSTR")

REMOTELOCALOPCODE: Final[dict[constants.RENLineOperation, str]] = {
constants.RENLineOperation.address_gtl: "justGTL",
constants.RENLineOperation.asrt: "enableRemote",
constants.RENLineOperation.asrt_address: "enableAndGotoRemote",
constants.RENLineOperation.asrt_address_llo: "enableAndGTRLLO",
constants.RENLineOperation.asrt_llo: "enableAndLockoutLocal",
constants.RENLineOperation.deassert: "disableRemote",
constants.RENLineOperation.deassert_gtl: "disableAndGTL",
}

# Override parsed to take into account the fact that this class is only used
# for a specific kind of resource
parsed: rname.TCPIPInstr
Expand Down Expand Up @@ -315,6 +325,36 @@ def write(self, data: bytes) -> Tuple[int, StatusCode]:

return len(data), StatusCode.success

def gpib_control_ren(self, mode: constants.RENLineOperation) -> StatusCode:
"""Controls the state of the GPIB Remote Enable (REN) interface line.

Optionally the remote/local state of the device is also controlled.

Corresponds to viGpibControlREN function of the VISA library.

Parameters
----------
mode : constants.RENLineOperation
Specifies the state of the REN line and optionally the device
remote/local state.

Returns
-------
StatusCode
Return value of the library call.

"""
try:
method = self.REMOTELOCALOPCODE[mode]
except Exception:
Comment thread
hb020 marked this conversation as resolved.
Outdated
# unknown value?
return StatusCode.error_nonsupported_operation

interface = cast(hislip.Instrument, self.interface)
interface.async_remote_local_control(method)

return StatusCode.success

def clear(self) -> StatusCode:
"""Clears a device.

Expand Down Expand Up @@ -853,6 +893,47 @@ def write(self, data: bytes) -> Tuple[int, StatusCode]:
except vxi11.Vxi11Error:
return 0, StatusCode.error_timeout

def gpib_control_ren(self, mode: constants.RENLineOperation) -> StatusCode:
"""Controls the state of the GPIB Remote Enable (REN) interface line.

Optionally the remote/local state of the device is also controlled.

Corresponds to viGpibControlREN function of the VISA library.

Parameters
----------
mode : constants.RENLineOperation
Specifies the state of the REN line and optionally the device
remote/local state.

Returns
-------
StatusCode
Return value of the library call.

"""
if mode not in (
constants.RENLineOperation.address_gtl,
constants.RENLineOperation.asrt_address,
constants.RENLineOperation.asrt_address_llo,
constants.RENLineOperation.deassert_gtl,
):
return constants.StatusCode.error_nonsupported_operation

if mode in (
constants.RENLineOperation.asrt_address,
constants.RENLineOperation.asrt_address_llo,
):
error = self.interface.device_remote(
self.link, 0, self.lock_timeout, self._io_timeout
)
else:
error = self.interface.device_local(
self.link, 0, self.lock_timeout, self._io_timeout
)

return VXI11_ERRORS_TO_VISA[error]
Comment thread
hb020 marked this conversation as resolved.
Outdated

def _get_attribute(self, attribute: ResourceAttribute) -> Tuple[Any, StatusCode]:
"""Get the value for a given VISA attribute for this session.

Expand Down
90 changes: 90 additions & 0 deletions pyvisa_py/testsuite/test_remote_local.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
"""Unit tests for VXI11 and hislip remote/local operations."""

from __future__ import annotations

from unittest.mock import MagicMock

import pytest

from pyvisa import constants
from pyvisa_py.tcpip import TCPIPInstrHiSLIP, TCPIPInstrVxi11


@pytest.mark.parametrize(
"mode, expected_method",
[
(constants.RENLineOperation.address_gtl, "device_local"),
(constants.RENLineOperation.asrt_address, "device_remote"),
(constants.RENLineOperation.asrt_address_llo, "device_remote"),
(constants.RENLineOperation.deassert_gtl, "device_local"),
],
)
def test_vxi11_gpib_control_ren_calls_expected_device_method(mode, expected_method):
session = object.__new__(TCPIPInstrVxi11)
session.interface = MagicMock()
session.link = 7
session.lock_timeout = 1234
session._io_timeout = 5678

session.interface.device_local.return_value = 0
session.interface.device_remote.return_value = 0

assert session.gpib_control_ren(mode) == constants.StatusCode.success

getattr(session.interface, expected_method).assert_called_once_with(
session.link, 0, session.lock_timeout, session._io_timeout
)

if expected_method == "device_remote":
session.interface.device_local.assert_not_called()
else:
session.interface.device_remote.assert_not_called()


@pytest.mark.parametrize("invalid_mode", [-1, 999, "bogus", None, object()])
def test_vxi11_gpib_control_ren_rejects_unsupported_modes(invalid_mode):
session = object.__new__(TCPIPInstrVxi11)
session.interface = MagicMock()
session.link = 7
session.lock_timeout = 1234
session._io_timeout = 5678

assert session.gpib_control_ren(invalid_mode) == (
constants.StatusCode.error_nonsupported_operation
)
session.interface.device_local.assert_not_called()
session.interface.device_remote.assert_not_called()


@pytest.mark.parametrize(
"mode, expected_method",
[
(constants.RENLineOperation.address_gtl, "justGTL"),
(constants.RENLineOperation.asrt, "enableRemote"),
(constants.RENLineOperation.asrt_address, "enableAndGotoRemote"),
(constants.RENLineOperation.asrt_address_llo, "enableAndGTRLLO"),
(constants.RENLineOperation.asrt_llo, "enableAndLockoutLocal"),
(constants.RENLineOperation.deassert, "disableRemote"),
(constants.RENLineOperation.deassert_gtl, "disableAndGTL"),
],
)
def test_hislip_gpib_control_ren_calls_expected_interface_method(mode, expected_method):
session = object.__new__(TCPIPInstrHiSLIP)
session.interface = MagicMock()

assert session.gpib_control_ren(mode) == constants.StatusCode.success
session.interface.async_remote_local_control.assert_called_once_with(
expected_method
)


@pytest.mark.parametrize("invalid_mode", [-1, 999, "bogus", None, object()])
def test_hislip_gpib_control_ren_rejects_unsupported_modes(invalid_mode):
session = object.__new__(TCPIPInstrHiSLIP)
session.interface = MagicMock()

assert session.gpib_control_ren(invalid_mode) == (
constants.StatusCode.error_nonsupported_operation
)
session.interface.async_remote_local_control.assert_not_called()
Loading