Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
25 changes: 25 additions & 0 deletions docs/source/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,31 @@ 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 NI-VISA does.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity with NI-VISA is good but did you check the visa specs ? Some of the above are a bit surprising for VXI-11 (LLO that works but does not lock is weird).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 rules:

RULE 6.5.6
An INSTR resource implementation of viGpibControlREN() for a TCPIP System SHALL support the
modes VI_GPIB_REN_DEASSERT_GTL, VI_GPIB_REN_ASSERT_ADDRESS, VI_GPIB_REN_ASSERT_ADRESS_LLO,
and VI_GPIB_REN_ADDRESS_GTL.

OBSERVATION 6.5.1
For a TCPIP device using VXI-11, the modes VI_GPIB_REN_DEASSERT_GTL and
VI_GPIB_REN_ADDRESS_GTL behave identically, putting the device into local mode. Similarly, the modes
VI_GPIB_REN_ASSERT_ADDRESS and VI_GPIB_REN_ASSERT_ADRESS_LLO behave identically, putting the
device into remote mode.

100% conform.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I'll adapt the faq to mention that


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
87 changes: 87 additions & 0 deletions pyvisa_py/tcpip.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,52 @@ 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.

"""
valid_modes = (
constants.RENLineOperation.address_gtl,
constants.RENLineOperation.asrt,
constants.RENLineOperation.asrt_address,
constants.RENLineOperation.asrt_address_llo,
constants.RENLineOperation.asrt_llo,
constants.RENLineOperation.deassert,
constants.RENLineOperation.deassert_gtl,
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a module level constant (private and marked Final).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overriden by later comment

if mode not in valid_modes:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could catch the KeyError when looking up the method to avoid 2 look ups.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

return StatusCode.error_nonsupported_operation

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",
}[mode]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, the dict should be a module level constant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overriden by later comment


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 +899,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