Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
79 changes: 79 additions & 0 deletions pyvisa_py/tcpip.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,44 @@ 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 = {
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.

The previous comment on making this a module/class constant still applies.

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.

moved. I kept the passage from one dict to another (in hislip.py), as this keeps it readable.

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.

done

except:
# 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 +891,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