diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | .travis.yml | 37 | ||||
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | CHANGES | 31 | ||||
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | docs/conf.py | 2 | ||||
-rw-r--r-- | docs/index.rst | 24 | ||||
-rw-r--r-- | pyvisa-py/common.py | 3 | ||||
-rw-r--r-- | pyvisa-py/gpib.py | 191 | ||||
-rw-r--r-- | pyvisa-py/highlevel.py | 185 | ||||
-rw-r--r-- | pyvisa-py/protocols/rpc.py | 287 | ||||
-rw-r--r-- | pyvisa-py/protocols/usbraw.py | 18 | ||||
-rw-r--r-- | pyvisa-py/protocols/usbtmc.py | 117 | ||||
-rw-r--r-- | pyvisa-py/protocols/usbutil.py | 9 | ||||
-rw-r--r-- | pyvisa-py/protocols/vxi11.py | 86 | ||||
-rw-r--r-- | pyvisa-py/serial.py | 76 | ||||
-rw-r--r-- | pyvisa-py/sessions.py | 348 | ||||
-rw-r--r-- | pyvisa-py/tcpip.py | 441 | ||||
-rw-r--r-- | pyvisa-py/testsuite/__init__.py | 6 | ||||
-rw-r--r-- | pyvisa-py/usb.py | 93 | ||||
-rw-r--r-- | setup.py | 26 |
21 files changed, 1389 insertions, 598 deletions
@@ -13,3 +13,7 @@ MANIFEST # WebDAV file system cache files .DAV/ _test/ +.spyproject/ +.mypy_cache/ +.pytest_cache/ +.cache/
\ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 2b115fb..37021e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,36 @@ language: python -python: - - "2.6" - - "2.7" - - "3.2" - - "3.3" - - "3.4" +# newer python versions are available only on xenial +# (while some older only on trusty) Ubuntu distribution +dist: xenial +sudo: required + +branches: + only: + # This is where pull requests from "bors r+" are built. + - staging + # This is where pull requests from "bors try" are built. + - trying + # Build master too + - master + +matrix: + include: + - python: 2.7 + dist: trusty + - python: 3.4 + dist: trusty + - python: 3.5 + dist: trusty + - python: 3.6 + - python: 3.7 install: - - if [ $TRAVIS_PYTHON_VERSION == '2.6' ]; then pip install unittest2; fi - - pip install coverage coveralls + - pip install coverage + - pip install coveralls script: - - if [ $TRAVIS_PYTHON_VERSION == '2.6' ]; then coverage run -p --source=pyvisa-py --omit="*test*" setup.py test; fi - - if [ $TRAVIS_PYTHON_VERSION != '2.6' ]; then python -bb -m coverage run -p --source=pyvisa-py --omit="*test*" setup.py test; fi + - python -bb -m coverage run -p --source=pyvisa-py --omit="*test*" setup.py test - coverage combine - coverage report -m @@ -8,6 +8,7 @@ Other contributors, listed alphabetically, are: * Colin Marquardt <github@marquardt-home.de> * Lance McCulley <lancemcculley@gmail.com> * Martin Ritter <ritter@mpp.mpg.de> +* Matthieu Dartiailh <marul@laposte.net> * Sebastian Held <sebastian.held@imst.de> * Thomas Kopp <20.kopp@gmail.com> * Thorsten Liebig <liebig@imst.de> @@ -2,6 +2,37 @@ PyVISA-py Changelog =================== +0.3 (2018-09-05) +---------------- + +- Fix handling of seesion registration under Python 3.7 PR #155 +- Add read_stb, assert_trigger, lock, unlock to highlevel PR #139 +- Fix timeout handling in usb PR #144 +- Add gpib_command and assert_trigger to GPIB PR # 136 +- Handle ValueError in usb list fix #131 PR #132 +- Fix reading on GPIB and implement clear and gpib_send_ifc PR #132 +- Do not error when listing USB devices PR #126 +- Fix an error in the handling of the termchar for TCPIP INSTR PR #126 +- Make list_resources return an empty tuple instead of erroring PR #121 +- Proper support for timeout in TCPIP INSTR sessions PR #120 #127 #130 #144 +- Proper encoding of data before transfer for all backends PR #119 +- Unify use of StatusCode PR #118 +- Improve handling of sessions attrs PR #116 +- TCPIP SOCKET timeout handling improvement PR #115 +- Fix compatibility with pyserial 3.0 PR #112 +- TCPIP SOCKET handler read should not block PR #107 +- TCPIP error handling fixes PR #100 +- Use repr() instead of str() to log RPC record PR #97 +- Speed up large transfer over GPIB 2beb52a5bcea2dae32d4a9908dc19f7874bfc0b7 +- Catch GPIB errors while enumerating devices 9fea9d5c40cc6c33ce1244c209e5e576a33abfc2 +- Add a serial poll function to GPIB backend PR #67 +- Handle timeout in USB TMC backend PR #64 +- Make USB TMC backend faster by transferring multiple bytes PR #63 +- Fix issue with encoding before data transfer PR #59 # +- Get Linux GPIB version PR #55 +- Fix broken import in TCPIP sessions PR #51 + + 0.2 (2015-08-25) ---------------- @@ -38,7 +38,7 @@ Python has a couple of features that make it very interesting for measurement co Requirements ------------ -- Python (tested with 2.6 and 2.7, 3.2+) +- Python (tested with 2.7, 3.4+) - PyVISA 1.6+ Optionally diff --git a/docs/conf.py b/docs/conf.py index 86a548c..896fd6c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -45,10 +45,10 @@ master_doc = 'index' # General information about the project.
project = 'PyVISA'
author = 'PyVISA Authors'
+
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
-
version = pkg_resources.get_distribution(project).version
release = version
this_year = datetime.date.today().year
diff --git a/docs/index.rst b/docs/index.rst index 862532f..ca73826 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -50,6 +50,8 @@ It depends on the interface type. For **ASRL** and **USB** we use PySerial_ and respectively. For **TCPIP** we use the :py:mod:`socket` module in the Python Standard Library. **GPIB** resources are not currently supported but they are in the plan using `linux-gpib`_. +PySerial_ version 3.0 or newer is required. + If I only need **TCPIP**, do I need to install PySerial and PyUSB? ------------------------------------------------------------------ @@ -75,24 +77,18 @@ Now: - ASRL INSTR - USB INSTR - TCPIP INSTR - -Soon we will be supporting: - - USB RAW - TCPIP SOCKET - -And later: - - GPIB INSTR -If you want that `soon` or `later` becomes now, give us a hand! Are all VISA attributes and methods implemented? ------------------------------------------------ -No. We have implemented those attributes and methods that are most commonly needed. -We would like to reach feature parity. If there is something that you need, let us know. +No. We have implemented those attributes and methods that are most commonly +needed. We would like to reach feature parity. If there is something that you +need, let us know. Why are you developing this? @@ -105,9 +101,9 @@ We wanted to provide a compatible alternative. Why not using LibreVISA? ------------------------ -LibreVISA_ is still young. However, you can already use it with the NI backend as it -has the same API. We think that PyVISA-py is easier to hack and we can quickly -reach feature parity with NI-VISA for message-based instruments. +LibreVISA_ is still young. However, you can already use it with the NI backend +as it has the same API. We think that PyVISA-py is easier to hack and we can +quickly reach feature parity with NI-VISA for message-based instruments. Why putting PyVISA in the middle? @@ -125,9 +121,9 @@ from higher level applications. .. _PyVISA: http://pyvisa.readthedocs.org/ .. _PyUSB: http://walac.github.io/pyusb/ .. _PyPI: https://pypi.python.org/pypi/PyVISA-py -.. _GitHub: https://github.com/hgrecco/pyvisa-py +.. _GitHub: https://github.com/pyvisa/pyvisa-py .. _`National Instruments's VISA`: http://ni.com/visa/ .. _`LibreVISA`: http://www.librevisa.org/ -.. _`issue tracker`: https://github.com/hgrecco/pyvisa-py/issues +.. _`issue tracker`: https://github.com/pyvisa/pyvisa-py/issues .. _`linux-gpib`: http://linux-gpib.sourceforge.net/ diff --git a/pyvisa-py/common.py b/pyvisa-py/common.py index 1f91543..acf9edc 100644 --- a/pyvisa-py/common.py +++ b/pyvisa-py/common.py @@ -9,7 +9,8 @@ :license: MIT, see LICENSE for more details. """ -from __future__ import division, unicode_literals, print_function, absolute_import +from __future__ import (division, unicode_literals, print_function, + absolute_import) import logging import sys diff --git a/pyvisa-py/gpib.py b/pyvisa-py/gpib.py index ef495d0..a80201b 100644 --- a/pyvisa-py/gpib.py +++ b/pyvisa-py/gpib.py @@ -13,18 +13,16 @@ from __future__ import division, unicode_literals, print_function, absolute_import from bisect import bisect -from pyvisa import constants, logger +from pyvisa import constants, logger, attributes from .sessions import Session, UnknownAttribute try: import gpib from Gpib import Gpib - except ImportError as e: Session.register_unavailable(constants.InterfaceType.gpib, 'INSTR', 'Please install linux-gpib to use this resource type.\n%s' % e) - raise @@ -32,17 +30,18 @@ def _find_listeners(): """Find GPIB listeners. """ for i in range(31): - if gpib.listener(BOARD, i) and gpib.ask(BOARD, 1) != i: - yield i + try: + if gpib.listener(BOARD, i) and gpib.ask(BOARD, 1) != i: + yield i + except gpib.GpibError as e: + logger.debug("GPIB error in _find_listeners(): %s", repr(e)) StatusCode = constants.StatusCode -SUCCESS = StatusCode.success - -# linux-gpib timeout constants, in milliseconds. See self.timeout. -TIMETABLE = (0, 1e-2, 3e-2, 1e-1, 3e-1, 1e0, 3e0, 1e1, 3e1, 1e2, 3e2, 1e3, 3e3, - 1e4, 3e4, 1e5, 3e5, 1e6) +# linux-gpib timeout constants, in seconds. See GPIBSession._set_timeout. +TIMETABLE = (0, 10e-6, 30e-6, 100e-6, 300e-6, 1e-3, 3e-3, 10e-3, 30e-3, 100e-3, 300e-3, 1.0, 3.0, + 10.0, 30.0, 100.0, 300.0, 1000.0) # TODO: Check board indices other than 0. BOARD = 0 @@ -56,22 +55,41 @@ class GPIBSession(Session): def list_resources(): return ['GPIB0::%d::INSTR' % pad for pad in _find_listeners()] - def after_parsing(self): - minor = self.parsed.board - pad = self.parsed.primary_address - self.handle = gpib.dev(int(minor), int(pad)) - self.interface = Gpib(self.handle) - - @property - def timeout(self): + @classmethod + def get_low_level_info(cls): + try: + ver = gpib.version() + except AttributeError: + ver = '< 4.0' - # 0x3 is the hexadecimal reference to the IbaTMO (timeout) configuration - # option in linux-gpib. - return TIMETABLE[self.interface.ask(3)] + return 'via Linux GPIB (%s)' % ver - @timeout.setter - def timeout(self, value): + def after_parsing(self): + minor = int(self.parsed.board) + pad = int(self.parsed.primary_address) + sad = 0 + timeout = 13 + send_eoi = 1 + eos_mode = 0 + self.interface = Gpib(name=minor, pad=pad, sad=sad, timeout=timeout, send_eoi=send_eoi, eos_mode=eos_mode) + self.controller = Gpib(name=minor) # this is the bus controller device + self.handle = self.interface.id + # force timeout setting to interface + self.set_attribute(constants.VI_ATTR_TMO_VALUE, attributes.AttributesByID[constants.VI_ATTR_TMO_VALUE].default) + + def _get_timeout(self, attribute): + if self.interface: + # 0x3 is the hexadecimal reference to the IbaTMO (timeout) configuration + # option in linux-gpib. + gpib_timeout = self.interface.ask(3) + if gpib_timeout and gpib_timeout < len(TIMETABLE): + self.timeout = TIMETABLE[gpib_timeout] + else: + # value is 0 or out of range -> infinite + self.timeout = None + return super(GPIBSession, self)._get_timeout(attribute) + def _set_timeout(self, attribute, value): """ linux-gpib only supports 18 discrete timeout values. If a timeout value other than these is requested, it will be rounded up to the closest @@ -96,7 +114,16 @@ class GPIBSession(Session): 16 300 seconds 17 1000 seconds """ - self.interface.timeout(bisect(TIMETABLE, value)) + status = super(GPIBSession, self)._set_timeout(attribute, value) + if self.interface: + if self.timeout is None: + gpib_timeout = 0 + else: + # round up only values that are higher by 0.1% than discrete values + gpib_timeout = min(bisect(TIMETABLE, 0.999 * self.timeout), 17) + self.timeout = TIMETABLE[gpib_timeout] + self.interface.timeout(gpib_timeout) + return status def close(self): gpib.close(self.handle) @@ -114,7 +141,7 @@ class GPIBSession(Session): # 0x2000 = 8192 = END checker = lambda current: self.interface.ibsta() & 8192 - reader = lambda: self.interface.read(1) + reader = lambda: self.interface.read(count) return self._read(reader, count, checker, False, None, False, gpib.GpibError) @@ -133,8 +160,9 @@ class GPIBSession(Session): try: self.interface.write(data) + count = self.interface.ibcnt() # number of bytes transmitted - return SUCCESS + return count, StatusCode.success except gpib.GpibError: # 0x4000 = 16384 = TIMO @@ -143,6 +171,85 @@ class GPIBSession(Session): else: return 0, StatusCode.error_system_error + def clear(self): + """Clears a device. + + Corresponds to viClear function of the VISA library. + + :param session: Unique logical identifier to a session. + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + + logger.debug('GPIB.device clear') + + try: + self.interface.clear() + return 0, StatusCode.success + except Exception: + return 0, StatusCode.error_system_error + + def gpib_command(self, command_byte): + """Write GPIB command byte on the bus. + + Corresponds to viGpibCommand function of the VISA library. + See: https://linux-gpib.sourceforge.io/doc_html/gpib-protocol.html#REFERENCE-COMMAND-BYTES + + :param command_byte: command byte to send + :type command_byte: int, must be [0 255] + :return: return value of the library call + :rtype: :class:`pyvisa.constants.StatusCode` + """ + + if 0 <= command_byte <= 255: + data = chr(command_byte) + else: + return StatusCode.error_nonsupported_operation + + try: + self.controller.command(data) + return StatusCode.success + + except gpib.GpibError: + return StatusCode.error_system_error + + def trigger(self, protocol): + """Asserts hardware trigger. + Only supports protocol = constants.VI_TRIG_PROT_DEFAULT + + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + + logger.debug('GPIB.device assert hardware trigger') + + try: + if protocol == constants.VI_TRIG_PROT_DEFAULT: + self.interface.trigger() + return StatusCode.success + else: + return StatusCode.error_nonsupported_operation + except gpib.GpibError: + return StatusCode.error_system_error + + def gpib_send_ifc(self): + """Pulse the interface clear line (IFC) for at least 100 microseconds. + + Corresponds to viGpibSendIFC function of the VISA library. + + :param session: Unique logical identifier to a session. + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + + logger.debug('GPIB.interface clear') + + try: + self.controller.interface_clear() + return 0, StatusCode.success + except: + return 0, StatusCode.error_system_error + def _get_attribute(self, attribute): """Get the value for a given VISA attribute for this session. @@ -156,20 +263,20 @@ class GPIBSession(Session): if attribute == constants.VI_ATTR_GPIB_READDR_EN: # IbaREADDR 0x6 # Setting has no effect in linux-gpib. - return self.interface.ask(6), SUCCESS + return self.interface.ask(6), StatusCode.success elif attribute == constants.VI_ATTR_GPIB_PRIMARY_ADDR: # IbaPAD 0x1 - return self.interface.ask(1), SUCCESS + return self.interface.ask(1), StatusCode.success elif attribute == constants.VI_ATTR_GPIB_SECONDARY_ADDR: # IbaSAD 0x2 # Remove 0x60 because National Instruments. sad = self.interface.ask(2) if self.interface.ask(2): - return self.interface.ask(2) - 96, SUCCESS + return self.interface.ask(2) - 96, StatusCode.success else: - return constants.VI_NO_SEC_ADDR, SUCCESS + return constants.VI_NO_SEC_ADDR, StatusCode.success elif attribute == constants.VI_ATTR_GPIB_REN_STATE: # I have no idea how to implement this. @@ -178,23 +285,23 @@ class GPIBSession(Session): elif attribute == constants.VI_ATTR_GPIB_UNADDR_EN: # IbaUnAddr 0x1b if self.interface.ask(27): - return constants.VI_TRUE, SUCCESS + return constants.VI_TRUE, StatusCode.success else: - return constants.VI_FALSE, SUCCESS + return constants.VI_FALSE, StatusCode.success elif attribute == constants.VI_ATTR_SEND_END_EN: # IbaEndBitIsNormal 0x1a if self.interface.ask(26): - return constants.VI_TRUE, SUCCESS + return constants.VI_TRUE, StatusCode.success else: - return constants.VI_FALSE, SUCCESS + return constants.VI_FALSE, StatusCode.success elif attribute == constants.VI_ATTR_INTF_NUM: # IbaBNA 0x200 - return self.interface.ask(512), SUCCESS + return self.interface.ask(512), StatusCode.success elif attribute == constants.VI_ATTR_INTF_TYPE: - return constants.InterfaceType.gpib, SUCCESS + return constants.InterfaceType.gpib, StatusCode.success raise UnknownAttribute(attribute) @@ -214,7 +321,7 @@ class GPIBSession(Session): # Setting has no effect in linux-gpib. if isinstance(attribute_state, int): self.interface.config(6, attribute_state) - return SUCCESS + return StatusCode.success else: return StatusCode.error_nonsupported_attribute_state @@ -222,7 +329,7 @@ class GPIBSession(Session): # IbcPAD 0x1 if isinstance(attribute_state, int) and 0 <= attribute_state <= 30: self.interface.config(1, attribute_state) - return SUCCESS + return StatusCode.success else: return StatusCode.error_nonsupported_attribute_state @@ -232,7 +339,7 @@ class GPIBSession(Session): if isinstance(attribute_state, int) and 0 <= attribute_state <= 30: if self.interface.ask(2): self.interface.config(2, attribute_state + 96) - return SUCCESS + return StatusCode.success else: return StatusCode.error_nonsupported_attribute else: @@ -242,7 +349,7 @@ class GPIBSession(Session): # IbcUnAddr 0x1b try: self.interface.config(27, attribute_state) - return SUCCESS + return StatusCode.success except gpib.GpibError: return StatusCode.error_nonsupported_attribute_state @@ -250,9 +357,11 @@ class GPIBSession(Session): # IbcEndBitIsNormal 0x1a if isinstance(attribute_state, int): self.interface.config(26, attribute_state) - return SUCCESS + return StatusCode.success else: return StatusCode.error_nonsupported_attribute_state raise UnknownAttribute(attribute) + def read_stb(self): + return self.interface.serial_poll() diff --git a/pyvisa-py/highlevel.py b/pyvisa-py/highlevel.py index 1753e01..33d1201 100644 --- a/pyvisa-py/highlevel.py +++ b/pyvisa-py/highlevel.py @@ -22,6 +22,8 @@ from pyvisa.compat import integer_types, OrderedDict from . import sessions from .common import logger +StatusCode = constants.StatusCode + class PyVisaLibrary(highlevel.VisaLibraryBase): """A pure Python backend for PyVISA. @@ -42,25 +44,25 @@ class PyVisaLibrary(highlevel.VisaLibraryBase): try: from .serial import SerialSession logger.debug('SerialSession was correctly imported.') - except ImportError as e: + except Exception as e: logger.debug('SerialSession was not imported %s.' % e) try: from .usb import USBSession, USBRawSession logger.debug('USBSession and USBRawSession were correctly imported.') - except ImportError as e: + except Exception as e: logger.debug('USBSession and USBRawSession were not imported %s.' % e) try: - from .tcpip import TCPIPSession + from .tcpip import TCPIPInstrSession, TCPIPSocketSession logger.debug('TCPIPSession was correctly imported.') - except ImportError as e: + except Exception as e: logger.debug('TCPIPSession was not imported %s.' % e) try: from .gpib import GPIBSession logger.debug('GPIBSession was correctly imported.') - except ImportError as e: + except Exception as e: logger.debug('GPIBSession was not imported %s.' % e) @classmethod @@ -122,7 +124,7 @@ class PyVisaLibrary(highlevel.VisaLibraryBase): extra=self._logging_extra) try: - ret_value = constants.StatusCode(ret_value) + ret_value = StatusCode(ret_value) except ValueError: pass @@ -162,7 +164,8 @@ class PyVisaLibrary(highlevel.VisaLibraryBase): # noinspection PyShadowingBuiltins def open(self, session, resource_name, - access_mode=constants.AccessModes.no_lock, open_timeout=constants.VI_TMO_IMMEDIATE): + access_mode=constants.AccessModes.no_lock, + open_timeout=constants.VI_TMO_IMMEDIATE): """Opens a session to the specified resource. Corresponds to viOpen function of the VISA library. @@ -184,13 +187,105 @@ class PyVisaLibrary(highlevel.VisaLibraryBase): try: parsed = rname.parse_resource_name(resource_name) except rname.InvalidResourceName: - return 0, constants.StatusCode.error_invalid_resource_name + return 0, StatusCode.error_invalid_resource_name cls = sessions.Session.get_session_class(parsed.interface_type_const, parsed.resource_class) - sess = cls(session, resource_name, parsed) + sess = cls(session, resource_name, parsed, open_timeout) + + return self._register(sess), StatusCode.success + + def assert_trigger(self, session, protocol): + """Asserts software or hardware trigger. + + Corresponds to viAssertTrigger function of the VISA library. + + :param session: Unique logical identifier to a session. + :param protocol: Trigger protocol to use during assertion. (Constants.PROT*) + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + try: + sess = self.sessions[session] + except KeyError: + return 0, constants.StatusCode.error_invalid_object + return sess.assert_trigger(protocol) + + def clear(self, session): + """Clears a device. + + Corresponds to viClear function of the VISA library. + + :param session: Unique logical identifier to a session. + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + try: + sess = self.sessions[session] + except KeyError: + return 0, constants.StatusCode.error_invalid_object + return sess.clear() + + def gpib_command(self, session, command_byte): + """Write GPIB command byte on the bus. + + Corresponds to viGpibCommand function of the VISA library. + See: https://linux-gpib.sourceforge.io/doc_html/gpib-protocol.html#REFERENCE-COMMAND-BYTES + + :param command_byte: command byte to send + :type command_byte: int, must be [0 255] + :return: return value of the library call + :rtype: :class:`pyvisa.constants.StatusCode` + """ + try: + return self.sessions[session].gpib_command(command_byte) + + except KeyError: + return constants.StatusCode.error_invalid_object + + def assert_trigger(self, session, protocol): + """Asserts software or hardware trigger. + + Corresponds to viAssertTrigger function of the VISA library. + + :param session: Unique logical identifier to a session. + :param protocol: Trigger protocol to use during assertion. (Constants.PROT*) + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + try: + return self.sessions[session].trigger(protocol) + + except KeyError: + return constants.StatusCode.error_invalid_object + + def gpib_send_ifc(self, session): + """Pulse the interface clear line (IFC) for at least 100 microseconds. + + Corresponds to viGpibSendIFC function of the VISA library. + + :param session: Unique logical identifier to a session. + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + try: + sess = self.sessions[session] + except KeyError: + return 0, constants.StatusCode.error_invalid_object + return sess.gpib_send_ifc() - return self._register(sess), constants.StatusCode.success + def read_stb(self, session): + """Reads a status byte of the service request. + Corresponds to viReadSTB function of the VISA library. + :param session: Unique logical identifier to a session. + :return: Service request status byte, return value of the library call. + :rtype: int, :class:`pyvisa.constants.StatusCode` + """ + try: + sess = self.sessions[session] + except KeyError: + return 0, constants.StatusCode.error_invalid_object + return sess.read_stb() def close(self, session): """Closes the specified session, event, or find list. @@ -206,7 +301,7 @@ class PyVisaLibrary(highlevel.VisaLibraryBase): if sess is not self: sess.close() except KeyError: - return constants.StatusCode.error_invalid_object + return StatusCode.error_invalid_object def open_default_resource_manager(self): """This function returns a session to the Default Resource Manager resource. @@ -216,7 +311,7 @@ class PyVisaLibrary(highlevel.VisaLibraryBase): :return: Unique logical identifier to a Default Resource Manager session, return value of the library call. :rtype: session, VISAStatus """ - return self._register(self), constants.StatusCode.success + return self._register(self), StatusCode.success def list_resources(self, session, query='?*::INSTR'): """Returns a tuple of all connected devices matching query. @@ -232,10 +327,7 @@ class PyVisaLibrary(highlevel.VisaLibraryBase): resources = rname.filter(resources, query) - if resources: - return resources - - raise errors.VisaIOError(errors.StatusCode.error_resource_not_found.value) + return resources def read(self, session, count): """Reads data from device or interface synchronously. @@ -250,9 +342,14 @@ class PyVisaLibrary(highlevel.VisaLibraryBase): # from the session handle, dispatch to the read method of the session object. try: - return self.sessions[session].read(count) + ret = self.sessions[session].read(count) except KeyError: - return constants.StatusCode.error_invalid_object + return 0, StatusCode.error_invalid_object + + if ret[1] < 0: + raise errors.VisaIOError(ret[1]) + + return ret def write(self, session, data): """Writes data to device or interface synchronously. @@ -268,9 +365,14 @@ class PyVisaLibrary(highlevel.VisaLibraryBase): # from the session handle, dispatch to the write method of the session object. try: - return self.sessions[session].write(data) + ret = self.sessions[session].write(data) except KeyError: - return constants.StatusCode.error_invalid_object + return 0, StatusCode.error_invalid_object + + if ret[1] < 0: + raise errors.VisaIOError(ret[1]) + + return ret def get_attribute(self, session, attribute): """Retrieves the state of an attribute. @@ -285,7 +387,7 @@ class PyVisaLibrary(highlevel.VisaLibraryBase): try: sess = self.sessions[session] except KeyError: - return None, constants.StatusCode.error_invalid_object + return None, StatusCode.error_invalid_object return sess.get_attribute(attribute) @@ -304,10 +406,46 @@ class PyVisaLibrary(highlevel.VisaLibraryBase): try: sess = self.sessions[session] except KeyError: - return constants.StatusCode.error_invalid_object + return StatusCode.error_invalid_object return sess.set_attribute(attribute, attribute_state) - + + def lock(self, session, lock_type, timeout, requested_key=None): + """Establishes an access mode to the specified resources. + + Corresponds to viLock function of the VISA library. + + :param session: Unique logical identifier to a session. + :param lock_type: Specifies the type of lock requested, either Constants.EXCLUSIVE_LOCK or Constants.SHARED_LOCK. + :param timeout: Absolute time period (in milliseconds) that a resource waits to get unlocked by the + locking session before returning an error. + :param requested_key: This parameter is not used and should be set to VI_NULL when lockType is VI_EXCLUSIVE_LOCK. + :return: access_key that can then be passed to other sessions to share the lock, return value of the library call. + :rtype: str, :class:`pyvisa.constants.StatusCode` + """ + try: + sess = self.sessions[session] + except KeyError: + return StatusCode.error_invalid_object + + return sess.lock(lock_type, timeout, requested_key) + + def unlock(self, session): + """Relinquishes a lock for the specified resource. + + Corresponds to viUnlock function of the VISA library. + + :param session: Unique logical identifier to a session. + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + try: + sess = self.sessions[session] + except KeyError: + return StatusCode.error_invalid_object + + return sess.unlock() + def disable_event(self, session, event_type, mechanism): # TODO: implement this for GPIB finalization pass @@ -315,4 +453,3 @@ class PyVisaLibrary(highlevel.VisaLibraryBase): def discard_events(self, session, event_type, mechanism): # TODO: implement this for GPIB finalization pass - diff --git a/pyvisa-py/protocols/rpc.py b/pyvisa-py/protocols/rpc.py index 66fe51c..c5c1245 100644 --- a/pyvisa-py/protocols/rpc.py +++ b/pyvisa-py/protocols/rpc.py @@ -13,21 +13,23 @@ XXX The UDP version of the protocol resends requests when it does XXX not receive a timely reply -- use only for idempotent calls! - XXX There is no provision for call timeout on TCP connections - - Original source: http://svn.python.org/projects/python/trunk/Demo/rpc/rpc.py + Original source: + http://svn.python.org/projects/python/trunk/Demo/rpc/rpc.py :copyright: 2014 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ -from __future__ import division, unicode_literals, print_function, absolute_import +from __future__ import (division, unicode_literals, print_function, + absolute_import) import sys import enum import xdrlib import socket +import select +import time from pyvisa.compat import struct @@ -197,7 +199,8 @@ class Unpacker(xdrlib.Unpacker): if stat == RejectStatus.rpc_mismatch: low = self.unpack_uint() high = self.unpack_uint() - raise RPCUnpackError('denied: rpc_mismatch: %r' % ((low, high),)) + raise RPCUnpackError('denied: rpc_mismatch: %r' % + ((low, high),)) if stat == RejectStatus.auth_error: stat = self.unpack_uint() raise RPCUnpackError('denied: auth_error: %r' % (stat,)) @@ -211,7 +214,8 @@ class Unpacker(xdrlib.Unpacker): if stat == AcceptStatus.program_mismatch: low = self.unpack_uint() high = self.unpack_uint() - raise RPCUnpackError('call failed: program_mismatch: %r' % ((low, high),)) + raise RPCUnpackError('call failed: program_mismatch: %r' % + ((low, high),)) if stat == AcceptStatus.procedure_unavailable: raise RPCUnpackError('call failed: procedure_unavailable') if stat == AcceptStatus.garbage_args: @@ -237,7 +241,8 @@ class Client(object): def make_call(self, proc, args, pack_func, unpack_func): # Don't normally override this (but see Broadcast) - logger.debug('Make call %r, %r, %r, %r', proc, args, pack_func, unpack_func) + logger.debug('Make call %r, %r, %r, %r', + proc, args, pack_func, unpack_func) if pack_func is None and args is not None: raise TypeError('non-null args with null pack_func') @@ -254,12 +259,12 @@ class Client(object): def start_call(self, proc): # Don't override this - self.lastxid = xid = self.lastxid + 1 + self.lastxid += 1 cred = self.mkcred() verf = self.mkverf() p = self.packer p.reset() - p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf) + p.pack_callheader(self.lastxid, self.prog, self.vers, proc, cred, verf) def do_call(self): # This MUST be overridden @@ -292,80 +297,200 @@ def sendfrag(sock, last, frag): sock.send(header + frag) -def sendrecord(sock, record): - logger.debug('Sending record through %s: %s', sock, record) - sendfrag(sock, 1, record) - - -def recvfrag(sock): - header = sock.recv(4) - if len(header) < 4: - raise EOFError - x = struct.unpack(">I", header[0:4])[0] - last = ((x & 0x80000000) != 0) - n = int(x & 0x7fffffff) - frag = b'' - while n > 0: - buf = sock.recv(n) - if not buf: - raise EOFError - n = n - len(buf) - frag = frag + buf - return last, frag +def _sendrecord(sock, record, fragsize=None, timeout=None): + logger.debug('Sending record through %s: %r', sock, record) + if timeout is not None: + r, w, x = select.select([], [sock], [], timeout) + if sock not in w: + msg = ("socket.timeout: The instrument seems to have stopped " + "responding.") + raise socket.timeout(msg) - -def recvrecord(sock): - record = b'' - last = 0 + last = False + if not fragsize: + fragsize = 0x7fffffff while not last: - last, frag = recvfrag(sock) - record = record + frag - - logger.debug('Received record through %s: %r', sock, record) - - return record + record_len = len(record) + if record_len <= fragsize: + fragsize = record_len + last = True + if last: + fragsize = fragsize | 0x80000000 + header = struct.pack(">I", fragsize) + sock.send(header + record[:fragsize]) + record = record[fragsize:] + + +def _recvrecord(sock, timeout, read_fun=None): + + record = bytearray() + buffer = bytearray() + if not read_fun: + read_fun = sock.recv + + wait_header = True + last = False + exp_length = 4 + + # minimum is in interval 1 - 100ms based on timeout or for infinite it is + # 1 sec + min_select_timeout = (max(min(timeout/100.0, 0.1), 0.001) + if timeout is not None else 1.0) + # initial 'select_timout' is half of timeout or max 2 secs + # (max blocking time). + # min is from 'min_select_timeout' + select_timout = (max(min(timeout/2.0, 2.0), min_select_timeout) + if timeout is not None else 1.0) + # time, when loop shall finish + finish_time = time.time() + timeout if timeout is not None else 0 + while True: + + # use select to wait for read ready, max `select_timout` seconds + r, w, x = select.select([sock], [], [], select_timout) + read_data = b'' + if sock in r: + read_data = read_fun(exp_length) + buffer.extend(read_data) + elif timeout is not None and time.time() >= finish_time: + # reached timeout + logger.debug(('Time out encountered in %s.' + 'Already receieved %d bytes. Last fragment is %d ' + 'bytes long and we were expecting %d'), + sock, len(record), len(buffer), exp_length) + msg = ("socket.timeout: The instrument seems to have stopped " + "responding.") + raise socket.timeout(msg) + else: + # `select_timout` decreased to 50% of previous or + # min_select_timeout + select_timout = max(select_timout/2.0, min_select_timeout) + continue + + if wait_header: + # need to find header + if len(buffer) >= exp_length: + header = buffer[:exp_length] + buffer = buffer[exp_length:] + x = struct.unpack(">I", header)[0] + last = ((x & 0x80000000) != 0) + exp_length = int(x & 0x7fffffff) + wait_header = False + else: + if len(buffer) >= exp_length: + record.extend(buffer[:exp_length]) + buffer = buffer[exp_length:] + if last: + logger.debug('Received record through %s: %r', + sock, record) + return bytes(record) + else: + wait_header = True + exp_length = 4 + + +def _connect(sock, host, port, timeout=0): + try: + sock.setblocking(0) + sock.connect_ex((host, port)) + except Exception as e: + sock.close() + return False + finally: + sock.setblocking(1) + + # minimum is in interval 100 - 500ms based on timeout + min_select_timeout = max(min(timeout/10.0, 0.5), 0.1) + # initial 'select_timout' is half of timeout or max 2 secs + # (max blocking time). + # min is from 'min_select_timeout' + select_timout = max(min(timeout/2.0, 2.0), min_select_timeout) + # time, when loop shall finish + finish_time = time.time() + timeout + while True: + # use select to wait for socket ready, max `select_timout` seconds + r, w, x = select.select([sock], [sock], [], select_timout) + if sock in r or sock in w: + return True + + if time.time() >= finish_time: + # reached timeout + return False + + # `select_timout` decreased to 50% of previous or min_select_timeout + select_timout = max(select_timout/2.0, min_select_timeout) class RawTCPClient(Client): """Client using TCP to a specific port. + """ - def __init__(self, host, prog, vers, port): + def __init__(self, host, prog, vers, port, open_timeout=5000): Client.__init__(self, host, prog, vers, port) - self.connect() - - def connect(self): - logger.debug('RawTCPClient: connecting to socket at (%s, %s)', self.host, self.port) + self.connect((open_timeout / 1000.0) + 1.0) + # self.timeout defaults higher than the default 2 second VISA timeout, + # ensuring that VISA timeouts take precedence. + self.timeout = 4.0 + + def make_call(self, proc, args, pack_func, unpack_func): + """Overridden to allow for utilizing io_timeout (passed in args). + + """ + if proc == 11: + # vxi11.DEVICE_WRITE + self.timeout = (args[1] / 1000.0) + 2.0 + elif proc in (12, 22): + # vxi11.DEVICE_READ or vxi11.DEVICE_DOCMD + self.timeout = (args[2] / 1000.0) + 2.0 + elif proc in (13, 14, 15, 16, 17): + # vxi11.DEVICE_READSTB, vxi11.DEVICE_TRIGGER, vxi11.DEVICE_CLEAR, + # vxi11.DEVICE_REMOTE, or vxi11.DEVICE_LOCAL + self.timeout = (args[3] / 1000.0) + 2.0 + else: + self.timeout = 4.0 + + return super(RawTCPClient, self).make_call(proc, args, pack_func, + unpack_func) + + def connect(self, timeout=5.0): + logger.debug('RawTCPClient: connecting to socket at (%s, %s)', + self.host, self.port) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.connect((self.host, self.port)) - + if not _connect(self.sock, self.host, self.port, timeout): + raise RPCError('can\'t connect to server') + def close(self): logger.debug('RawTCPClient: closing socket') self.sock.close() - + def do_call(self): call = self.packer.get_buf() - sendrecord(self.sock, call) - reply = recvrecord(self.sock) + + _sendrecord(self.sock, call, timeout=self.timeout) + + reply = _recvrecord(self.sock, self.timeout) u = self.unpacker u.reset(reply) xid, verf = u.unpack_replyheader() if xid != self.lastxid: # Can't really happen since this is TCP... - raise RPCError('wrong xid in reply %r instead of %r' % (xid, self.lastxid)) + msg = 'wrong xid in reply {0} instead of {1}' + raise RPCError(msg.format(xid, self.lastxid)) -# Client using UDP to a specific port - class RawUDPClient(Client): + """Client using UDP to a specific port. + + """ def __init__(self, host, prog, vers, port): Client.__init__(self, host, prog, vers, port) self.connect() - + def connect(self): - logger.debug('RawTCPClient: connecting to socket at (%s, %s)', self.host, self.port) + logger.debug('RawTCPClient: connecting to socket at (%s, %s)', + self.host, self.port) self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.connect((self.host, self.port)) - + def close(self): logger.debug('RawTCPClient: closing socket') self.sock.close() @@ -373,18 +498,14 @@ class RawUDPClient(Client): def do_call(self): call = self.packer.get_buf() self.sock.send(call) - try: - from select import select - except ImportError: - logger.warn('select not found, RPC may hang') - select = None + BUFSIZE = 8192 # Max UDP buffer size timeout = 1 count = 5 while 1: r, w, x = [self.sock], [], [] if select: - r, w, x = select(r, w, x, timeout) + r, w, x = select.select(r, w, x, timeout) if self.sock not in r: count = count - 1 if count < 0: @@ -410,7 +531,7 @@ class RawBroadcastUDPClient(RawUDPClient): RawUDPClient.__init__(self, bcastaddr, prog, vers, port) self.reply_handler = None self.timeout = 30 - + def connect(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) @@ -429,11 +550,7 @@ class RawBroadcastUDPClient(RawUDPClient): pack_func(args) call = self.packer.get_buf() self.sock.sendto(call, (self.host, self.port)) - try: - from select import select - except ImportError: - logger.warn('select not found, broadcast will hang') - select = None + BUFSIZE = 8192 # Max UDP buffer size (for reply) replies = [] if unpack_func is None: @@ -444,9 +561,9 @@ class RawBroadcastUDPClient(RawUDPClient): r, w, x = [self.sock], [], [] if select: if self.timeout is None: - r, w, x = select(r, w, x) + r, w, x = select.select(r, w, x) else: - r, w, x = select(r, w, x, self.timeout) + r, w, x = select.select(r, w, x, self.timeout) if self.sock not in r: break reply, fromaddr = self.sock.recvfrom(BUFSIZE) @@ -485,8 +602,8 @@ class PortMapperVersion(enum.IntEnum): #: (call_args) -> call_result call_it = 5 -# A mapping is (prog, vers, prot, port) and prot is one of: +# A mapping is (prog, vers, prot, port) and prot is one of: IPPROTO_TCP = 6 IPPROTO_UDP = 17 @@ -569,8 +686,9 @@ class PartialPortMapperClient(object): class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient): - def __init__(self, host): - RawTCPClient.__init__(self, host, PMAP_PROG, PMAP_VERS, PMAP_PORT) + def __init__(self, host, open_timeout=5000): + RawTCPClient.__init__(self, host, PMAP_PROG, PMAP_VERS, PMAP_PORT, + open_timeout) PartialPortMapperClient.__init__(self) @@ -581,23 +699,25 @@ class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient): PartialPortMapperClient.__init__(self) -class BroadcastUDPPortMapperClient(PartialPortMapperClient, RawBroadcastUDPClient): +class BroadcastUDPPortMapperClient(PartialPortMapperClient, + RawBroadcastUDPClient): def __init__(self, bcastaddr): - RawBroadcastUDPClient.__init__(self, bcastaddr, PMAP_PROG, PMAP_VERS, PMAP_PORT) + RawBroadcastUDPClient.__init__(self, bcastaddr, PMAP_PROG, PMAP_VERS, + PMAP_PORT) PartialPortMapperClient.__init__(self) class TCPClient(RawTCPClient): """A TCP Client that find their server through the Port mapper """ - def __init__(self, host, prog, vers): - pmap = TCPPortMapperClient(host) + def __init__(self, host, prog, vers, open_timeout=5000): + pmap = TCPPortMapperClient(host, open_timeout) port = pmap.get_port((prog, vers, IPPROTO_TCP, 0)) pmap.close() if port == 0: raise RPCError('program not registered') - RawTCPClient.__init__(self, host, prog, vers, port) + RawTCPClient.__init__(self, host, prog, vers, port, open_timeout) class UDPClient(RawUDPClient): @@ -653,7 +773,8 @@ class BroadcastUDPClient(Client): self.unpack_func = unpack_func self.replies = [] packed_args = self.packer.get_buf() - dummy_replies = self.pmap.Callit((self.prog, self.vers, proc, packed_args)) + dummy_replies = self.pmap.Callit((self.prog, self.vers, proc, + packed_args)) return self.replies @@ -757,7 +878,7 @@ class TCPServer(Server): def __init__(self, host, prog, vers, port): Server.__init__(self, host, prog, vers, port) self.connect() - + def connect(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.prot = IPPROTO_TCP @@ -772,7 +893,7 @@ class TCPServer(Server): sock, (host, port) = connection while 1: try: - call = recvrecord(sock) + call = _recvrecord(sock, None) except EOFError: break except socket.error: @@ -780,7 +901,7 @@ class TCPServer(Server): break reply = self.handle(call) if reply is not None: - sendrecord(sock, reply) + _sendrecord(sock, reply) def forkingloop(self): # Like loop but uses forksession() @@ -816,12 +937,12 @@ class UDPServer(Server): def __init__(self, host, prog, vers, port): Server.__init__(self, host, prog, vers, port) self.connect() - + def connect(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.prot = IPPROTO_UDP self.sock.bind((self.host, self.port)) - + def loop(self): while 1: self.session() diff --git a/pyvisa-py/protocols/usbraw.py b/pyvisa-py/protocols/usbraw.py index 0e06c4a..9ed8e25 100644 --- a/pyvisa-py/protocols/usbraw.py +++ b/pyvisa-py/protocols/usbraw.py @@ -18,18 +18,18 @@ from __future__ import division, unicode_literals, print_function, absolute_impo from .usbtmc import USBRaw as USBRaw -import usb +from .usbutil import find_devices, find_interfaces -from .usbutil import find_devices, find_interfaces, find_endpoint, usb_find_desc - -def find_raw_devices(vendor=None, product=None, serial_number=None, custom_match=None, **kwargs): +def find_raw_devices(vendor=None, product=None, serial_number=None, + custom_match=None, **kwargs): """Find connected USB RAW devices. See usbutil.find_devices for more info. """ def is_usbraw(dev): if custom_match and not custom_match(dev): return False - return bool(find_interfaces(dev, bInterfaceClass=0xFF, bInterfaceSubClass=0xFF)) + return bool(find_interfaces(dev, bInterfaceClass=0xFF, + bInterfaceSubClass=0xFF)) return find_devices(vendor, product, serial_number, is_usbraw, **kwargs) @@ -66,7 +66,6 @@ class USBRawDevice(USBRaw): return bytes_sent - def read(self, size): """Read raw bytes from the instrument. @@ -77,13 +76,12 @@ class USBRawDevice(USBRaw): """ raw_read = super(USBRawDevice, self).read - raw_write = super(USBRawDevice, self).write - received = b'' + received = bytearray() while not len(received) >= size: resp = raw_read(self.RECV_CHUNK) - received += resp + received.extend(resp) - return received + return bytes(received) diff --git a/pyvisa-py/protocols/usbtmc.py b/pyvisa-py/protocols/usbtmc.py index 20c53d2..472d274 100644 --- a/pyvisa-py/protocols/usbtmc.py +++ b/pyvisa-py/protocols/usbtmc.py @@ -5,7 +5,9 @@ Implements Session to control USBTMC instruments - Loosely based on PyUSBTMC:python module to handle USB-TMC(Test and Measurement class) devices. + Loosely based on PyUSBTMC:python module to handle USB-TMC(Test and + Measurement class) devices. + by Noboru Yamamot, Accl. Lab, KEK, JAPAN This file is an offspring of the Lantz Project. @@ -14,7 +16,8 @@ :license: MIT, see LICENSE for more details. """ -from __future__ import division, unicode_literals, print_function, absolute_import +from __future__ import (division, unicode_literals, print_function, + absolute_import) import enum from pyvisa.compat import struct @@ -23,7 +26,8 @@ from collections import namedtuple import usb -from .usbutil import find_devices, find_interfaces, find_endpoint, usb_find_desc +from .usbutil import (find_devices, find_interfaces, find_endpoint, + usb_find_desc) import sys @@ -58,35 +62,44 @@ class Request(enum.IntEnum): indicator_pulse = 64 -def find_tmc_devices(vendor=None, product=None, serial_number=None, custom_match=None, **kwargs): +def find_tmc_devices(vendor=None, product=None, serial_number=None, + custom_match=None, **kwargs): """Find connected USBTMC devices. See usbutil.find_devices for more info. + """ def is_usbtmc(dev): if custom_match and not custom_match(dev): return False - return bool(find_interfaces(dev, bInterfaceClass=0xfe, bInterfaceSubClass=3)) + return bool(find_interfaces(dev, bInterfaceClass=0xfe, + bInterfaceSubClass=3)) return find_devices(vendor, product, serial_number, is_usbtmc, **kwargs) class BulkOutMessage(object): - """The Host uses the Bulk-OUT endpoint to send USBTMC command messages to the device. + """The Host uses the Bulk-OUT endpoint to send USBTMC command messages to + the device. + """ @staticmethod def build_array(btag, eom, chunk): size = len(chunk) - return struct.pack('BBBx', MsgID.dev_dep_msg_out, btag, ~btag & 0xFF) + \ - struct.pack("<LBxxx", size, eom) + \ - chunk + \ - b'\0' * ((4 - size) % 4) + return (struct.pack('BBBx', MsgID.dev_dep_msg_out, btag, + ~btag & 0xFF) + + struct.pack("<LBxxx", size, eom) + + chunk + + b'\0' * ((4 - size) % 4)) class BulkInMessage(namedtuple('BulkInMessage', 'msgid btag btaginverse ' - 'transfer_size transfer_attributes data')): - """The Host uses the Bulk-IN endpoint to read USBTMC response messages from the device. - The Host must first send a USBTMC command message that expects a response before - attempting to read a USBTMC response message. + 'transfer_size transfer_attributes data')): + """The Host uses the Bulk-IN endpoint to read USBTMC response messages from + the device. + + The Host must first send a USBTMC command message that expects a response + before attempting to read a USBTMC response message. + """ @classmethod @@ -94,10 +107,12 @@ class BulkInMessage(namedtuple('BulkInMessage', 'msgid btag btaginverse ' msgid, btag, btaginverse = struct.unpack_from('BBBx', data) assert msgid == MsgID.dev_dep_msg_in - transfer_size, transfer_attributes = struct.unpack_from('<LBxxx', data, 4) + transfer_size, transfer_attributes = struct.unpack_from('<LBxxx', data, + 4) data = data[12:] - return cls(msgid, btag, btaginverse, transfer_size, transfer_attributes, data) + return cls(msgid, btag, btaginverse, transfer_size, + transfer_attributes, data) @staticmethod def build_array(btag, transfer_size, term_char=None): @@ -115,8 +130,10 @@ class BulkInMessage(namedtuple('BulkInMessage', 'msgid btag btaginverse ' else: transfer_attributes = 2 - return struct.pack('BBBx', MsgID.request_dev_dep_msg_in, btag, ~btag & 0xFF) + \ - struct.pack("<LBBxx", transfer_size, transfer_attributes, term_char) + return (struct.pack('BBBx', MsgID.request_dev_dep_msg_in, btag, + ~btag & 0xFF) + + struct.pack("<LBBxx", transfer_size, transfer_attributes, + term_char)) class USBRaw(object): @@ -126,10 +143,12 @@ class USBRaw(object): #: Configuration number to be used. If None, the default will be used. CONFIGURATION = None + #: Interface index it be used INTERFACE = (0, 0) - #: Receive and Send endpoints to be used. If None the first IN (or OUT) BULK - #: endpoint will be used. + + #: Receive and Send endpoints to be used. If None the first IN (or OUT) + #: BULK endpoint will be used. ENDPOINTS = (None, None) timeout = 2000 @@ -143,16 +162,17 @@ class USBRaw(object): self.timeout = timeout device_filters = device_filters or {} - devices = list(self.find_devices(vendor, product, serial_number, None, **device_filters)) + devices = list(self.find_devices(vendor, product, serial_number, None, + **device_filters)) if not devices: raise ValueError('No device found.') elif len(devices) > 1: desc = '\n'.join(str(dev) for dev in devices) - raise ValueError('{} devices found:\n{}\n' - 'Please narrow the search criteria'.format(len(devices), desc)) + raise ValueError('{} devices found:\n{}\nPlease narrow the search' + ' criteria'.format(len(devices), desc)) - self.usb_dev, other = devices[0], devices[1:] + self.usb_dev = devices[0] try: if self.usb_dev.is_kernel_driver_active(0): @@ -172,7 +192,8 @@ class USBRaw(object): self.usb_intf = self._find_interface(self.usb_dev, self.INTERFACE) - self.usb_recv_ep, self.usb_send_ep = self._find_endpoints(self.usb_intf, self.ENDPOINTS) + self.usb_recv_ep, self.usb_send_ep =\ + self._find_endpoints(self.usb_intf, self.ENDPOINTS) def _find_interface(self, dev, setting): return self.usb_dev.get_active_configuration()[self.INTERFACE] @@ -180,12 +201,14 @@ class USBRaw(object): def _find_endpoints(self, interface, setting): recv, send = setting if recv is None: - recv = find_endpoint(interface, usb.ENDPOINT_IN, usb.ENDPOINT_TYPE_BULK) + recv = find_endpoint(interface, usb.ENDPOINT_IN, + usb.ENDPOINT_TYPE_BULK) else: recv = usb_find_desc(interface, bEndpointAddress=recv) if send is None: - send = find_endpoint(interface, usb.ENDPOINT_OUT, usb.ENDPOINT_TYPE_BULK) + send = find_endpoint(interface, usb.ENDPOINT_OUT, + usb.ENDPOINT_TYPE_BULK) else: send = usb_find_desc(interface, bEndpointAddress=send) @@ -228,9 +251,11 @@ class USBTMC(USBRaw): find_devices = staticmethod(find_tmc_devices) - def __init__(self, vendor=None, product=None, serial_number=None, **kwargs): + def __init__(self, vendor=None, product=None, serial_number=None, + **kwargs): super(USBTMC, self).__init__(vendor, product, serial_number, **kwargs) - self.usb_intr_in = find_endpoint(self.usb_intf, usb.ENDPOINT_IN, usb.ENDPOINT_TYPE_INTERRUPT) + self.usb_intr_in = find_endpoint(self.usb_intf, usb.ENDPOINT_IN, + usb.ENDPOINT_TYPE_INTERRUPT) self.usb_dev.reset() self.usb_dev.set_configuration() @@ -242,21 +267,23 @@ class USBTMC(USBRaw): self._btag = 0 if not (self.usb_recv_ep and self.usb_send_ep): - raise ValueError("TMC device must have both Bulk-In and Bulk-out endpoints.") + msg = "TMC device must have both Bulk-In and Bulk-out endpoints." + raise ValueError(msg) def _get_capabilities(self): - cap = self.usb_dev.ctrl_transfer( - usb.util.build_request_type(usb.util.CTRL_IN, - usb.util.CTRL_TYPE_CLASS, - usb.util.CTRL_RECIPIENT_INTERFACE), - Request.get_capabilities, - 0x0000, - self.usb_intf.index, - 0x0018, - timeout=self.timeout) + self.usb_dev.ctrl_transfer( + usb.util.build_request_type(usb.util.CTRL_IN, + usb.util.CTRL_TYPE_CLASS, + usb.util.CTRL_RECIPIENT_INTERFACE), + Request.get_capabilities, + 0x0000, + self.usb_intf.index, + 0x0018, + timeout=self.timeout) def _find_interface(self, dev, setting): - interfaces = find_interfaces(dev, bInterfaceClass=0xFE, bInterfaceSubClass=3) + interfaces = find_interfaces(dev, bInterfaceClass=0xFE, + bInterfaceSubClass=3) if not interfaces: raise ValueError('USB TMC interface not found.') elif len(interfaces) > 1: @@ -281,13 +308,13 @@ class USBTMC(USBRaw): self._btag = (self._btag % 255) + 1 - data = BulkOutMessage.build_array(self._btag, end > size, data[begin:end]) + data = BulkOutMessage.build_array(self._btag, end > size, + data[begin:end]) bytes_sent += raw_write(data) return bytes_sent - def read(self, size): recv_chunk = self.RECV_CHUNK @@ -297,7 +324,7 @@ class USBTMC(USBRaw): raw_read = super(USBTMC, self).read raw_write = super(USBTMC, self).write - received = b'' + received = bytearray() while not eom: self._btag = (self._btag % 255) + 1 @@ -310,8 +337,8 @@ class USBTMC(USBRaw): response = BulkInMessage.from_bytes(resp) - received += response.data + received.extend(response.data) eom = response.transfer_attributes & 1 - return received + return bytes(received) diff --git a/pyvisa-py/protocols/usbutil.py b/pyvisa-py/protocols/usbutil.py index 44791df..e02ce07 100644 --- a/pyvisa-py/protocols/usbutil.py +++ b/pyvisa-py/protocols/usbutil.py @@ -120,10 +120,6 @@ AllCodes = { } -class LantzUSBTimeoutError(usb.core.USBError): - pass - - def ep_attributes(ep): c = ep.bmAttributes attrs = [] @@ -136,7 +132,7 @@ def ep_attributes(ep): attrs.append('Bulk') elif tp == usb.ENDPOINT_TYPE_INTERRUPT: attrs.append('Interrupt') - + sync = (c & 12) >> 2 if sync == 0: attrs.append('No sync') @@ -159,7 +155,8 @@ def ep_attributes(ep): return ', '.join(attrs) -def find_devices(vendor=None, product=None, serial_number=None, custom_match=None, **kwargs): +def find_devices(vendor=None, product=None, serial_number=None, + custom_match=None, **kwargs): """Find connected USB devices matching certain keywords. Wildcards can be used for vendor, product and serial_number. diff --git a/pyvisa-py/protocols/vxi11.py b/pyvisa-py/protocols/vxi11.py index 0026b06..69d2a7f 100644 --- a/pyvisa-py/protocols/vxi11.py +++ b/pyvisa-py/protocols/vxi11.py @@ -13,9 +13,11 @@ :license: MIT, see LICENSE for more details. """ -from __future__ import division, unicode_literals, print_function, absolute_import +from __future__ import (division, unicode_literals, print_function, + absolute_import) import enum +import socket from . import rpc @@ -69,6 +71,7 @@ class ErrorCodes(enum.IntEnum): abort = 23 channel_already_established = 29 + # Flags OP_FLAG_WAIT_BLOCK = 1 OP_FLAG_END = 8 @@ -87,14 +90,14 @@ class Vxi11Packer(rpc.Packer): def pack_device_link(self, link): self.pack_int(link) - + def pack_create_link_parms(self, params): id, lock_device, lock_timeout, device = params self.pack_int(id) self.pack_bool(lock_device) self.pack_uint(lock_timeout) self.pack_string(device.encode('ascii')) - + def pack_device_write_parms(self, params): link, io_timeout, lock_timeout, flags, data = params self.pack_int(link) @@ -102,7 +105,7 @@ class Vxi11Packer(rpc.Packer): self.pack_uint(lock_timeout) self.pack_int(flags) self.pack_opaque(data) - + def pack_device_read_parms(self, params): link, request_size, io_timeout, lock_timeout, flags, term_char = params self.pack_int(link) @@ -111,14 +114,14 @@ class Vxi11Packer(rpc.Packer): self.pack_uint(lock_timeout) self.pack_int(flags) self.pack_int(term_char) - + def pack_device_generic_parms(self, params): link, flags, lock_timeout, io_timeout = params self.pack_int(link) self.pack_int(flags) self.pack_uint(lock_timeout) self.pack_uint(io_timeout) - + def pack_device_remote_func_parms(self, params): host_addr, host_port, prog_num, prog_vers, prog_family = params self.pack_uint(host_addr) @@ -126,7 +129,7 @@ class Vxi11Packer(rpc.Packer): self.pack_uint(prog_num) self.pack_uint(prog_vers) self.pack_int(prog_family) - + def pack_device_enable_srq_parms(self, params): link, enable, handle = params self.pack_int(link) @@ -134,15 +137,16 @@ class Vxi11Packer(rpc.Packer): if len(handle) > 40: raise Vxi11Error("array length too long") self.pack_opaque(handle) - + def pack_device_lock_parms(self, params): link, flags, lock_timeout = params self.pack_int(link) self.pack_int(flags) self.pack_uint(lock_timeout) - + def pack_device_docmd_parms(self, params): - link, flags, io_timeout, lock_timeout, cmd, network_order, datasize, data_in = params + (link, flags, io_timeout, lock_timeout, + cmd, network_order, datasize, data_in) = params self.pack_int(link) self.pack_int(flags) self.pack_uint(io_timeout) @@ -156,33 +160,33 @@ class Vxi11Packer(rpc.Packer): class Vxi11Unpacker(rpc.Unpacker): def unpack_device_link(self): return self.unpack_int() - + def unpack_device_error(self): return self.unpack_int() - + def unpack_create_link_resp(self): error = self.unpack_int() link = self.unpack_int() abort_port = self.unpack_uint() max_recv_size = self.unpack_uint() return error, link, abort_port, max_recv_size - + def unpack_device_write_resp(self): error = self.unpack_int() size = self.unpack_uint() return error, size - + def unpack_device_read_resp(self): error = self.unpack_int() reason = self.unpack_int() data = self.unpack_opaque() return error, reason, data - + def unpack_device_read_stb_resp(self): error = self.unpack_int() stb = self.unpack_uint() return error, stb - + def unpack_device_docmd_resp(self): error = self.unpack_int() data_out = self.unpack_opaque() @@ -191,10 +195,11 @@ class Vxi11Unpacker(rpc.Unpacker): class CoreClient(rpc.TCPClient): - def __init__(self, host): + def __init__(self, host, open_timeout=5000): self.packer = Vxi11Packer() self.unpacker = Vxi11Unpacker('') - super(CoreClient, self).__init__(host, DEVICE_CORE_PROG, DEVICE_CORE_VERS) + super(CoreClient, self).__init__(host, DEVICE_CORE_PROG, + DEVICE_CORE_VERS, open_timeout) def create_link(self, id, lock_device, lock_timeout, name): params = (id, lock_device, lock_timeout, name) @@ -204,15 +209,23 @@ class CoreClient(rpc.TCPClient): def device_write(self, link, io_timeout, lock_timeout, flags, data): params = (link, io_timeout, lock_timeout, flags, data) - return self.make_call(DEVICE_WRITE, params, - self.packer.pack_device_write_parms, - self.unpacker.unpack_device_write_resp) - - def device_read(self, link, request_size, io_timeout, lock_timeout, flags, term_char): - params = (link, request_size, io_timeout, lock_timeout, flags, term_char) - return self.make_call(DEVICE_READ, params, - self.packer.pack_device_read_parms, - self.unpacker.unpack_device_read_resp) + try: + return self.make_call(DEVICE_WRITE, params, + self.packer.pack_device_write_parms, + self.unpacker.unpack_device_write_resp) + except socket.timeout as e: + return ErrorCodes.io_timeout, e.args[0], '' + + def device_read(self, link, request_size, io_timeout, lock_timeout, flags, + term_char): + params = (link, request_size, io_timeout, lock_timeout, flags, + term_char) + try: + return self.make_call(DEVICE_READ, params, + self.packer.pack_device_read_parms, + self.unpacker.unpack_device_read_resp) + except socket.timeout as e: + return ErrorCodes.io_timeout, e.args[0], '' def device_read_stb(self, link, flags, lock_timeout, io_timeout): params = (link, flags, lock_timeout, io_timeout) @@ -261,8 +274,10 @@ class CoreClient(rpc.TCPClient): self.packer.pack_device_enable_srq_parms, self.unpacker.unpack_device_error) - def device_docmd(self, link, flags, io_timeout, lock_timeout, cmd, network_order, datasize, data_in): - params = (link, flags, io_timeout, lock_timeout, cmd, network_order, datasize, data_in) + def device_docmd(self, link, flags, io_timeout, lock_timeout, cmd, + network_order, datasize, data_in): + params = (link, flags, io_timeout, lock_timeout, cmd, network_order, + datasize, data_in) return self.make_call(DEVICE_DOCMD, params, self.packer.pack_device_docmd_parms, self.unpacker.unpack_device_docmd_resp) @@ -271,20 +286,15 @@ class CoreClient(rpc.TCPClient): return self.make_call(DESTROY_LINK, link, self.packer.pack_device_link, self.unpacker.unpack_device_error) - - def create_intr_chan(self, host_addr, host_port, prog_num, prog_vers, prog_family): + + def create_intr_chan(self, host_addr, host_port, prog_num, prog_vers, + prog_family): params = (host_addr, host_port, prog_num, prog_vers, prog_family) return self.make_call(CREATE_INTR_CHAN, params, self.packer.pack_device_docmd_parms, self.unpacker.unpack_device_error) - + def destroy_intr_chan(self): return self.make_call(DESTROY_INTR_CHAN, None, None, self.unpacker.unpack_device_error) - - - - - - diff --git a/pyvisa-py/serial.py b/pyvisa-py/serial.py index 36ff2ab..12128b2 100644 --- a/pyvisa-py/serial.py +++ b/pyvisa-py/serial.py @@ -19,12 +19,10 @@ from . import common try: import serial - from serial import Serial from serial.tools.list_ports import comports except ImportError as e: Session.register_unavailable(constants.InterfaceType.asrl, 'INSTR', - 'Please install PySerial to use this resource type.\n%s' % e) - + 'Please install PySerial (>=3.0) to use this resource type.\n%s' % e) raise @@ -37,7 +35,6 @@ def to_state(boolean_input): StatusCode = constants.StatusCode -SUCCESS = StatusCode.success SerialTermination = constants.SerialTermination @@ -63,37 +60,26 @@ class SerialSession(Session): if 'mock' in self.parsed: cls = self.parsed.mock else: - cls = Serial + cls = serial.Serial - self.interface = cls(port=self.parsed.board, timeout=2000, writeTimeout=2000) + self.interface = cls(port=self.parsed.board, timeout=self.timeout, write_timeout=self.timeout) - for name in 'ASRL_END_IN,ASRL_END_OUT,SEND_END_EN,TERMCHAR,' \ - 'TERMCHAR_EN,SUPPRESS_END_EN'.split(','): + for name in ('ASRL_END_IN', 'ASRL_END_OUT', 'SEND_END_EN', 'TERMCHAR', + 'TERMCHAR_EN', 'SUPPRESS_END_EN'): attribute = getattr(constants, 'VI_ATTR_' + name) self.attrs[attribute] = attributes.AttributesByID[attribute].default - @property - def timeout(self): - value = self.interface.timeout - - if value is None: - return constants.VI_TMO_INFINITE - elif value == 0: - return constants.VI_TMO_IMMEDIATE - else: - return int(value * 1000) - - @timeout.setter - def timeout(self, value): - if value == constants.VI_TMO_INFINITE: - value = None - elif value == constants.VI_TMO_IMMEDIATE: - value = 0 - else: - value = value / 1000. + def _get_timeout(self, attribute): + if self.interface: + self.timeout = self.interface.timeout + return super(SerialSession, self)._get_timeout(attribute) - self.interface.timeout = value - self.interface.writeTimeout = value + def _set_timeout(self, attribute, value): + status = super(SerialSession, self)._set_timeout(attribute, value) + if self.interface: + self.interface.timeout = self.timeout + self.interface.write_timeout = self.timeout + return status def close(self): self.interface.close() @@ -172,7 +158,7 @@ class SerialSession(Session): logger.debug('Serial.sendBreak') self.interface.sendBreak() - return count, constants.StatusCode.success + return count, StatusCode.success except serial.SerialTimeoutException: return 0, StatusCode.error_timeout @@ -191,10 +177,10 @@ class SerialSession(Session): raise NotImplementedError elif attribute == constants.VI_ATTR_ASRL_AVAIL_NUM: - return self.interface.inWaiting(), SUCCESS + return self.interface.inWaiting(), StatusCode.success elif attribute == constants.VI_ATTR_ASRL_BAUD: - return self.interface.baudrate, SUCCESS + return self.interface.baudrate, StatusCode.success elif attribute == constants.VI_ATTR_ASRL_BREAK_LEN: raise NotImplementedError @@ -206,10 +192,10 @@ class SerialSession(Session): raise NotImplementedError elif attribute == constants.VI_ATTR_ASRL_CTS_STATE: - return to_state(self.interface.getCTS()), SUCCESS + return to_state(self.interface.getCTS()), StatusCode.success elif attribute == constants.VI_ATTR_ASRL_DATA_BITS: - return self.interface.bytesize, SUCCESS + return self.interface.bytesize, StatusCode.success elif attribute == constants.VI_ATTR_ASRL_DCD_STATE: raise NotImplementedError @@ -218,7 +204,7 @@ class SerialSession(Session): raise NotImplementedError elif attribute == constants.VI_ATTR_ASRL_DSR_STATE: - return to_state(self.interface.getDSR()), SUCCESS + return to_state(self.interface.getDSR()), StatusCode.success elif attribute == constants.VI_ATTR_ASRL_DTR_STATE: raise NotImplementedError @@ -226,20 +212,20 @@ class SerialSession(Session): elif attribute == constants.VI_ATTR_ASRL_FLOW_CNTRL: return (self.interface.xonxoff * constants.VI_ASRL_FLOW_XON_XOFF | self.interface.rtscts * constants.VI_ASRL_FLOW_RTS_CTS | - self.interface.dsrdtr * constants.VI_ASRL_FLOW_DTR_DSR), SUCCESS + self.interface.dsrdtr * constants.VI_ASRL_FLOW_DTR_DSR), StatusCode.success elif attribute == constants.VI_ATTR_ASRL_PARITY: parity = self.interface.parity if parity == serial.PARITY_NONE: - return constants.Parity.none, SUCCESS + return constants.Parity.none, StatusCode.success elif parity == serial.PARITY_EVEN: - return constants.Parity.even, SUCCESS + return constants.Parity.even, StatusCode.success elif parity == serial.PARITY_ODD: - return constants.Parity.odd, SUCCESS + return constants.Parity.odd, StatusCode.success elif parity == serial.PARITY_MARK: - return constants.Parity.mark, SUCCESS + return constants.Parity.mark, StatusCode.success elif parity == serial.PARITY_SPACE: - return constants.Parity.space, SUCCESS + return constants.Parity.space, StatusCode.success raise Exception('Unknown parity value: %r' % parity) @@ -252,11 +238,11 @@ class SerialSession(Session): elif attribute == constants.VI_ATTR_ASRL_STOP_BITS: bits = self.interface.stopbits if bits == serial.STOPBITS_ONE: - return constants.StopBits.one, SUCCESS + return constants.StopBits.one, StatusCode.success elif bits == serial.STOPBITS_ONE_POINT_FIVE: - return constants.StopBits.one_and_a_half, SUCCESS + return constants.StopBits.one_and_a_half, StatusCode.success elif bits == serial.STOPBITS_TWO: - return constants.StopBits.two, SUCCESS + return constants.StopBits.two, StatusCode.success raise Exception('Unknown bits value: %r' % bits) @@ -264,7 +250,7 @@ class SerialSession(Session): raise NotImplementedError elif attribute == constants.VI_ATTR_INTF_TYPE: - return constants.InterfaceType.asrl, SUCCESS + return constants.InterfaceType.asrl, StatusCode.success raise UnknownAttribute(attribute) diff --git a/pyvisa-py/sessions.py b/pyvisa-py/sessions.py index c389fa4..5674177 100644 --- a/pyvisa-py/sessions.py +++ b/pyvisa-py/sessions.py @@ -10,7 +10,8 @@ :license: MIT, see LICENSE for more details. """ -from __future__ import division, unicode_literals, print_function, absolute_import +from __future__ import (division, unicode_literals, print_function, + absolute_import) import abc import time @@ -20,6 +21,9 @@ from pyvisa import logger, constants, attributes, compat, rname from . import common +StatusCode = constants.StatusCode + + class UnknownAttribute(Exception): def __init__(self, attribute): @@ -45,7 +49,8 @@ class Session(compat.with_metaclass(abc.ABCMeta)): Just makes sure that common methods are defined and information is stored. - :param resource_manager_session: The session handle of the parent Resource Manager + :param resource_manager_session: The session handle of the parent Resource + Manager :param resource_name: The resource name. :param parsed: the parsed resource name (optional). If not provided, the resource_name will be parsed. @@ -58,7 +63,8 @@ class Session(compat.with_metaclass(abc.ABCMeta)): Use to implement custom logic for attributes. :param attribute: Resource attribute for which the state query is made - :return: The state of the queried attribute for a specified resource, return value of the library call. + :return: The state of the queried attribute for a specified resource, + return value of the library call. :rtype: (unicode | str | list | int, VISAStatus) """ @@ -79,7 +85,8 @@ class Session(compat.with_metaclass(abc.ABCMeta)): """Close the session. Use it to do final clean ups. """ - #: Maps (Interface Type, Resource Class) to Python class encapsulating that resource. + #: Maps (Interface Type, Resource Class) to Python class encapsulating that + #: resource. #: dict[(Interface Type, Resource Class) , Session] _session_classes = dict() @@ -122,7 +129,8 @@ class Session(compat.with_metaclass(abc.ABCMeta)): try: return cls._session_classes[(interface_type, resource_class)] except KeyError: - raise ValueError('No class registered for %s, %s' % (interface_type, resource_class)) + raise ValueError('No class registered for %s, %s' % + (interface_type, resource_class)) @classmethod def register(cls, interface_type, resource_class): @@ -133,35 +141,45 @@ class Session(compat.with_metaclass(abc.ABCMeta)): """ def _internal(python_class): if (interface_type, resource_class) in cls._session_classes: - logger.warning('%s is already registered in the ResourceManager. ' - 'Overwriting with %s' % ((interface_type, resource_class), python_class)) + logger.warning('%s is already registered in the ' + 'ResourceManager. Overwriting with %s', + ((interface_type, resource_class), python_class) + ) python_class.session_type = (interface_type, resource_class) - cls._session_classes[(interface_type, resource_class)] = python_class + cls._session_classes[(interface_type, + resource_class)] = python_class return python_class return _internal @classmethod def register_unavailable(cls, interface_type, resource_class, msg): - """Register an unavailable session class for a given interface type and resource class. + """Register an unavailable session class for a given interface type and + resource class. + raising a ValueError if called. :type interface_type: constants.InterfaceType :type resource_class: str """ # noinspection PyUnusedLocal - def _internal(*args, **kwargs): - raise ValueError(msg) + class _internal(object): + session_issue = msg + + def __init__(self, *args, **kwargs): + raise ValueError(msg) _internal.session_issue = msg if (interface_type, resource_class) in cls._session_classes: logger.warning('%s is already registered in the ResourceManager. ' - 'Overwriting with unavailable %s' % ((interface_type, resource_class), msg)) + 'Overwriting with unavailable %s', + ((interface_type, resource_class), msg)) cls._session_classes[(interface_type, resource_class)] = _internal - def __init__(self, resource_manager_session, resource_name, parsed=None): + def __init__(self, resource_manager_session, resource_name, parsed=None, + open_timeout=None): if isinstance(resource_name, common.MockInterface): parsed = rname.parse_resource_name(resource_name.resource_name) parsed['mock'] = resource_name @@ -170,31 +188,159 @@ class Session(compat.with_metaclass(abc.ABCMeta)): parsed = rname.parse_resource_name(resource_name) self.parsed = parsed + self.open_timeout = open_timeout - #: Used as a place holder for the object doing the lowlevel communication. + #: Get default timeout from constants + self.timeout =\ + (attributes.AttributesByID[constants.VI_ATTR_TMO_VALUE].default / + 1000.0) + + #: Used as a place holder for the object doing the lowlevel + #: communication. self.interface = None #: Used for attributes not handled by the underlying interface. - #: Values are get or set automatically by get_attribute and set_attribute + #: Values are get or set automatically by get_attribute and + #: set_attribute #: Add your own by overriding after_parsing. self.attrs = {constants.VI_ATTR_RM_SESSION: resource_manager_session, constants.VI_ATTR_RSRC_NAME: str(parsed), constants.VI_ATTR_RSRC_CLASS: parsed.resource_class, - constants.VI_ATTR_INTF_TYPE: parsed.interface_type} + constants.VI_ATTR_INTF_TYPE: parsed.interface_type, + constants.VI_ATTR_TMO_VALUE: (self._get_timeout, + self._set_timeout)} self.after_parsing() def after_parsing(self): """Override this method to provide custom initialization code, to be called after the resourcename is properly parsed + + ResourceSession can register resource specific attributes handling of + them into self.attrs. + It is also possible to change handling of already registerd common + attributes. List of attributes is available in pyvisa package: + * name is in constants module as: VI_ATTR_<NAME> + * validity of attribute for resource is defined module attributes, + AttrVI_ATTR_<NAME>.resources + + For static (read only) values, simple readonly and also readwrite + attributes simplified construction can be used: + ` self.attrs[constants.VI_ATTR_<NAME>] = 100` + or + ` self.attrs[constants.VI_ATTR_<NAME>] = <self.variable_name>` + + For more complex handling of attributes, it is possible to register + getter and/or setter. When Null is used, NotSupported error is + returned. + Getter has same signature as see Session._get_attribute and setter has + same signature as see Session._set_attribute. (It is possible to + register also see Session._get_attribute and see Session._set_attribute + as getter/setter). Getter and Setter are registered as tupple. + For readwrite attribute: + ` self.attrs[constants.VI_ATTR_<NAME>] = (<getter_name>, + <setter_name>)` + For readonly attribute: + ` self.attrs[constants.VI_ATTR_<NAME>] = (<getter_name>, None)` + For reusing of see Session._get_attribute and see + Session._set_attribute + ` self.attrs[constants.VI_ATTR_<NAME>] = (self._get_attribute, + self._set_attribute)` + """ + pass + + def gpib_command(self, command_byte): + """Write GPIB command byte on the bus. + + Corresponds to viGpibCommand function of the VISA library. + See: https://linux-gpib.sourceforge.io/doc_html/gpib-protocol.html#REFERENCE-COMMAND-BYTES + + :param command_byte: command byte to send + :type command_byte: int, must be [0 255] + :return: return value of the library call + :rtype: :class:`pyvisa.constants.StatusCode` + """ + try: + return self.sessions[session].gpib_command(command_byte) + + except KeyError: + return constants.StatusCode.error_invalid_object + + def assert_trigger(self, protocol): + """Asserts software or hardware trigger. + + Corresponds to viAssertTrigger function of the VISA library. + + :param protocol: Trigger protocol to use during assertion. (Constants.PROT*) + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + raise NotImplementedError + + def gpib_send_ifc(self): + """Pulse the interface clear line (IFC) for at least 100 microseconds. + + Corresponds to viGpibSendIFC function of the VISA library. + + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + return StatusCode.error_nonsupported_operation + + def clear(self): + """Clears a device. + + Corresponds to viClear function of the VISA library. + + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` """ + return StatusCode.error_nonsupported_operation + + def read_stb(self): + """Reads a status byte of the service request. + + Corresponds to viReadSTB function of the VISA library. + + :return: Service request status byte, return value of the library call. + :rtype: int, :class:`pyvisa.constants.StatusCode` + """ + return 0, StatusCode.error_nonsupported_operation + + def lock(self, session, lock_type, timeout, requested_key=None): + """Establishes an access mode to the specified resources. + + Corresponds to viLock function of the VISA library. + + :param session: Unique logical identifier to a session. + :param lock_type: Specifies the type of lock requested, either Constants.EXCLUSIVE_LOCK or Constants.SHARED_LOCK. + :param timeout: Absolute time period (in milliseconds) that a resource waits to get unlocked by the + locking session before returning an error. + :param requested_key: This parameter is not used and should be set to VI_NULL when lockType is VI_EXCLUSIVE_LOCK. + :return: access_key that can then be passed to other sessions to share the lock, return value of the library call. + :rtype: str, :class:`pyvisa.constants.StatusCode` + """ + return '', StatusCode.error_nonsupported_operation + + def unlock(self, session): + """Relinquishes a lock for the specified resource. + + Corresponds to viUnlock function of the VISA library. + + :param session: Unique logical identifier to a session. + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + return StatusCode.error_nonsupported_operation def get_attribute(self, attribute): """Get the value for a given VISA attribute for this session. - Does a few checks before and calls before dispatching to `_get_attribute`. + Does a few checks before and calls before dispatching to + `_get_attribute`. :param attribute: Resource attribute for which the state query is made - :return: The state of the queried attribute for a specified resource, return value of the library call. + :return: The state of the queried attribute for a specified resource, + return value of the library call. :rtype: (unicode | str | list | int, VISAStatus) """ @@ -202,36 +348,41 @@ class Session(compat.with_metaclass(abc.ABCMeta)): try: attr = attributes.AttributesByID[attribute] except KeyError: - return 0, constants.StatusCode.error_nonsupported_attribute + return 0, StatusCode.error_nonsupported_attribute # Check if the attribute is defined for this session type. if not attr.in_resource(self.session_type): - return 0, constants.StatusCode.error_nonsupported_attribute + return 0, StatusCode.error_nonsupported_attribute # Check if reading the attribute is allowed. if not attr.read: raise Exception('Do not now how to handle write only attributes.') - # First try to answer those attributes that are common to all session types - # or user defined because they are not defined by the interface. + # First try to answer those attributes that are registered in + # self.attrs, see Session.after_parsing if attribute in self.attrs: - return self.attrs[attribute], constants.StatusCode.success + value = self.attrs[attribute] + status = StatusCode.success + if isinstance(value, tuple): + getter = value[0] + value, status = (getter(attribute) if getter else + (0, StatusCode.error_nonsupported_attribute)) + return value, status - elif attribute == constants.VI_ATTR_TMO_VALUE: - return self.timeout, constants.StatusCode.success - - # Dispatch to `_get_attribute`, which must be implemented by subclasses. + # Dispatch to `_get_attribute`, which must be implemented by subclasses try: return self._get_attribute(attribute) except UnknownAttribute as e: logger.exception(str(e)) - return 0, constants.StatusCode.error_nonsupported_attribute + return 0, StatusCode.error_nonsupported_attribute def set_attribute(self, attribute, attribute_state): - """Set the attribute_state value for a given VISA attribute for this session. + """Set the attribute_state value for a given VISA attribute for this + session. - Does a few checks before and calls before dispatching to `_gst_attribute`. + Does a few checks before and calls before dispatching to + `_gst_attribute`. :param attribute: Resource attribute for which the state query is made. :param attribute_state: value. @@ -243,43 +394,42 @@ class Session(compat.with_metaclass(abc.ABCMeta)): try: attr = attributes.AttributesByID[attribute] except KeyError: - return constants.StatusCode.error_nonsupported_attribute + return StatusCode.error_nonsupported_attribute # Check if the attribute is defined for this session type. if not attr.in_resource(self.session_type): - return constants.StatusCode.error_nonsupported_attribute + return StatusCode.error_nonsupported_attribute # Check if writing the attribute is allowed. if not attr.write: - return constants.StatusCode.error_attribute_read_only + return StatusCode.error_attribute_read_only - # First try to answer those attributes that are common to all session types - # or user defined because they are not defined by the interface. + # First try to answer those attributes that are registered in + # self.attrs, see Session.after_parsing if attribute in self.attrs: - self.attrs[attribute] = attribute_state - return constants.StatusCode.success - - elif attribute == constants.VI_ATTR_TMO_VALUE: - try: - self.timeout = attribute_state - except: - return constants.StatusCode.error_nonsupported_attribute_state - - return constants.StatusCode.success - - # Dispatch to `_set_attribute`, which must be implemented by subclasses. + value = self.attrs[attribute] + status = StatusCode.success + if isinstance(value, tuple): + setter = value[1] + status = (setter(attribute, attribute_state) if setter else + StatusCode.error_nonsupported_attribute) + else: + self.attrs[attribute] = attribute_state + return status + + # Dispatch to `_set_attribute`, which must be implemented by subclasses try: return self._set_attribute(attribute, attribute_state) except ValueError: - return constants.StatusCode.error_nonsupported_attribute_state + return StatusCode.error_nonsupported_attribute_state except NotImplementedError: e = UnknownAttribute(attribute) logger.exception(str(e)) - return constants.StatusCode.error_nonsupported_attribute + return StatusCode.error_nonsupported_attribute except UnknownAttribute as e: logger.exception(str(e)) - return constants.StatusCode.error_nonsupported_attribute + return StatusCode.error_nonsupported_attribute def _read(self, reader, count, end_indicator_checker, suppress_end_en, termination_char, termination_char_en, timeout_exception): @@ -287,47 +437,101 @@ class Session(compat.with_metaclass(abc.ABCMeta)): Corresponds to viRead function of the VISA library. - :param reader: Function to read a single byte. - :type reader: () -> byte + :param reader: Function to read one or more bytes. + :type reader: () -> bytes :param count: Number of bytes to be read. :type count: int - :param end_indicator_checker: Function to check if the byte. - :type end_indicator_checker: (byte) -> boolean + :param end_indicator_checker: Function to check if the message is + complete. + :type end_indicator_checker: (bytes) -> boolean :param suppress_end_en: suppress end. :type suppress_end_en: bool - :param termination_char: Number of bytes to be read. + :param termination_char: Stop reading if this character is received. + :type suppress_end_en: int or str :param termination_char_en: termination char enabled. :type termination_char_en: boolean - :param: timeout_exception: Exception to capture time out for the given interface. + :param: timeout_exception: Exception to capture time out for the given + interface. :type: Exception :return: data read, return value of the library call. :rtype: bytes, constants.StatusCode """ - timeout = self.get_attribute(constants.VI_ATTR_TMO_VALUE)[0] / 1000. + # NOTE: Some interfaces return not only a single byte but a complete + # block for each read therefore we must handle the case that the + # termination character is in the middle of the block or that the + # maximum number of bytes is exceeded - start = time.time() - out = b'' + # Make sure termination_char is a string + try: + termination_char = chr(termination_char) + except TypeError: + pass + + finish_time = (None if self.timeout is None else + (time.time() + self.timeout)) + out = bytearray() while True: try: current = reader() except timeout_exception: - return out, constants.StatusCode.error_timeout + return out, StatusCode.error_timeout if current: - out += current + out.extend(current) end_indicator_received = end_indicator_checker(current) - if end_indicator_received and not suppress_end_en: - # RULE 6.1.1 - return out, constants.StatusCode.success + if end_indicator_received: + if not suppress_end_en: + # RULE 6.1.1 + return bytes(out), StatusCode.success + else: + if termination_char_en and termination_char in current: + # RULE 6.1.2 + # Return everything upto and including the termination + # character + return (bytes(out[:out.index(termination_char)+1]), + StatusCode.success_termination_character_read) + elif len(out) >= count: + # RULE 6.1.3 + # Return at most the number of bytes requested + return (bytes(out[:count]), + StatusCode.success_max_count_read) + + if finish_time and time.time() > finish_time: + return bytes(out), StatusCode.error_timeout + + def _get_timeout(self, attribute): + """ Returns timeout calculated value from python way to VI_ way + + In VISA, the timeout is expressed in milliseconds or using the + constants VI_TMO_INFINITE or VI_TMO_IMMEDIATE. + + In Python we store it as either None (VI_TMO_INFINITE), 0 + (VI_TMO_IMMEDIATE) or as a floating point number in seconds. + + """ + if self.timeout is None: + ret_value = constants.VI_TMO_INFINITE + elif self.timeout == 0: + ret_value = constants.VI_TMO_IMMEDIATE + else: + ret_value = int(self.timeout * 1000.0) + return ret_value, StatusCode.success + + def _set_timeout(self, attribute, value): + """ Sets timeout calculated value from python way to VI_ way - elif not end_indicator_received and current == termination_char and termination_char_en: - # RULE 6.1.2 - return out, constants.StatusCode.success_termination_character_read + In VISA, the timeout is expressed in milliseconds or using the + constants VI_TMO_INFINITE or VI_TMO_IMMEDIATE. - elif not end_indicator_received and current != termination_char and len(out) == count: - # RULE 6.1.3 - return out, constants.StatusCode.success_max_count_read + In Python we store it as either None (VI_TMO_INFINITE), 0 + (VI_TMO_IMMEDIATE) or as a floating point number in seconds. - if time.time() - start > timeout: - return out, constants.StatusCode.error_timeout + """ + if value == constants.VI_TMO_INFINITE: + self.timeout = None + elif value == constants.VI_TMO_IMMEDIATE: + self.timeout = 0 + else: + self.timeout = value / 1000.0 + return StatusCode.success diff --git a/pyvisa-py/tcpip.py b/pyvisa-py/tcpip.py index a862ef8..4b84140 100644 --- a/pyvisa-py/tcpip.py +++ b/pyvisa-py/tcpip.py @@ -15,29 +15,41 @@ import socket import select import time -from pyvisa import constants, attributes +from pyvisa import constants, attributes, errors from .sessions import Session, UnknownAttribute -from .protocols import vxi11 +from .protocols import vxi11, rpc from . import common StatusCode = constants.StatusCode -SUCCESS = StatusCode.success + +# Conversion between VXI11 error codes and VISA status +# TODO this is so far a best guess, in particular 6 and 29 are likely wrong +VXI11_ERRORS_TO_VISA =\ + {0: StatusCode.success, # no_error + 1: StatusCode.error_invalid_format, # syntax_error + 3: StatusCode.error_connection_lost, # device_no_accessible + 4: StatusCode.error_invalid_access_key, # invalid_link_identifier + 5: StatusCode.error_invalid_parameter, # parameter_error + 6: StatusCode.error_handler_not_installed, # channel_not_established + 8: StatusCode.error_nonsupported_operation, # operation_not_supported + 9: StatusCode.error_allocation, # out_of_resources + 11: StatusCode.error_resource_locked, # device_locked_by_another_link + 12: StatusCode.error_session_not_locked, # no_lock_held_by_this_link + 15: StatusCode.error_timeout, # io_timeout + 17: StatusCode.error_io, # io_error + 23: StatusCode.error_abort, # abort + 29: StatusCode.error_window_already_mapped, # channel_already_established + } @Session.register(constants.InterfaceType.tcpip, 'INSTR') class TCPIPInstrSession(Session): - """A TCPIP Session that uses the network standard library to do the low level communication - using VXI-11 - """ - - lock_timeout = 1000 - timeout = 1000 - client_id = None + """A TCPIP Session that uses the network standard library to do the low + level communication using VXI-11 - link = None - max_recv_size = 1024 + """ @staticmethod def list_resources(): @@ -47,16 +59,17 @@ class TCPIPInstrSession(Session): def after_parsing(self): # TODO: board_number not handled # TODO: lan_device_name not handled - self.interface = vxi11.CoreClient(self.parsed.host_address) + # vx11 expect all timeouts to be expressed in ms and should be integers + self.interface = vxi11.CoreClient(self.parsed.host_address, + self.open_timeout) + self.max_recv_size = 1024 self.lock_timeout = 10000 - self.timeout = 10000 self.client_id = random.getrandbits(31) - (error, link, - abort_port, - max_recv_size) = self.interface.create_link(self.client_id, 0, self.lock_timeout, - self.parsed.lan_device_name) + error, link, abort_port, max_recv_size =\ + self.interface.create_link(self.client_id, 0, self.lock_timeout, + self.parsed.lan_device_name) if error: raise Exception("error creating link: %d" % error) @@ -64,12 +77,17 @@ class TCPIPInstrSession(Session): self.link = link self.max_recv_size = min(max_recv_size, 2 ** 30) # 1GB - for name in 'SEND_END_EN,TERMCHAR,TERMCHAR_EN'.split(','): + for name in ('SEND_END_EN', 'TERMCHAR', 'TERMCHAR_EN'): attribute = getattr(constants, 'VI_ATTR_' + name) - self.attrs[attribute] = attributes.AttributesByID[attribute].default + self.attrs[attribute] =\ + attributes.AttributesByID[attribute].default def close(self): - self.interface.destroy_link(self.link) + try: + self.interface.destroy_link(self.link) + except (errors.VisaIOError, socket.error, rpc.RPCError) as e: + print("Error closing VISA link: {}".format(e)) + self.interface.close() self.link = None self.interface = None @@ -88,32 +106,35 @@ class TCPIPInstrSession(Session): else: chunk_length = self.max_recv_size - flags = 0 - reason = 0 - if self.get_attribute(constants.VI_ATTR_TERMCHAR_EN)[0]: term_char, _ = self.get_attribute(constants.VI_ATTR_TERMCHAR) - else: - term_char = 0 - - if term_char: flags = vxi11.OP_FLAG_TERMCHAR_SET - term_char = str(term_char).encode('utf-8')[0] + else: + term_char = flags = 0 read_data = bytearray() - + reason = 0 + # Stop on end of message or when a termination character has been + # encountered. end_reason = vxi11.RX_END | vxi11.RX_CHR - read_fun = self.interface.device_read + status = StatusCode.success - status = SUCCESS - + timeout = self._io_timeout + start_time = time.time() while reason & end_reason == 0: - error, reason, data = read_fun(self.link, chunk_length, self.timeout, + # Decrease timeout so that the total timeout does not get larger + # than the specified timeout. + timeout = max(0, + timeout - int((time.time() - start_time)*1000)) + error, reason, data = read_fun(self.link, chunk_length, + timeout, self.lock_timeout, flags, term_char) - if error: - return read_data, StatusCode.error_io + if error == vxi11.ErrorCodes.io_timeout: + return bytes(read_data), StatusCode.error_timeout + elif error: + return bytes(read_data), StatusCode.error_io read_data.extend(data) count -= len(data) @@ -133,42 +154,44 @@ class TCPIPInstrSession(Session): :param data: data to be written. :type data: str - :return: Number of bytes actually transferred, return value of the library call. + :return: Number of bytes actually transferred, return value of the + library call. :rtype: int, VISAStatus """ send_end, _ = self.get_attribute(constants.VI_ATTR_SEND_END_EN) - chunk_size = 1024 - try: + try: if send_end: flags = vxi11.OP_FLAG_TERMCHAR_SET else: flags = 0 num = len(data) - offset = 0 while num > 0: if num <= chunk_size: flags |= vxi11.OP_FLAG_END - block = data[offset:offset+self.max_recv_size] + block = data[offset:offset + self.max_recv_size] - error, size = self.interface.device_write(self.link, self.timeout, - self.lock_timeout, flags, block) + error, size = self.interface.device_write( + self.link, self._io_timeout, self.lock_timeout, + flags, block) - if error: - return offset, StatusCode.error_io - elif size < len(block): + if error == vxi11.ErrorCodes.io_timeout: + return offset, StatusCode.error_timeout + + elif error or size < len(block): return offset, StatusCode.error_io offset += size num -= size - return offset, SUCCESS + return offset, StatusCode.success + except vxi11.Vxi11Error: return 0, StatusCode.error_timeout @@ -178,12 +201,13 @@ class TCPIPInstrSession(Session): Use to implement custom logic for attributes. :param attribute: Resource attribute for which the state query is made - :return: The state of the queried attribute for a specified resource, return value of the library call. + :return: The state of the queried attribute for a specified resource, + return value of the library call. :rtype: (unicode | str | list | int, VISAStatus) """ if attribute == constants.VI_ATTR_TCPIP_ADDR: - return self.host_address, SUCCESS + return self.host_address, StatusCode.success elif attribute == constants.VI_ATTR_TCPIP_DEVICE_NAME: raise NotImplementedError @@ -210,8 +234,10 @@ class TCPIPInstrSession(Session): Corresponds to viSetAttribute function of the VISA library. - :param attribute: Attribute for which the state is to be modified. (Attributes.*) - :param attribute_state: The state of the attribute to be set for the specified object. + :param attribute: Attribute for which the state is to be modified. + (Attributes.*) + :param attribute_state: The state of the attribute to be set for the + specified object. :return: return value of the library call. :rtype: VISAStatus """ @@ -223,20 +249,16 @@ class TCPIPInstrSession(Session): Corresponds to viAssertTrigger function of the VISA library. - :param protocol: Trigger protocol to use during assertion. (Constants.PROT*) + :param protocol: Trigger protocol to use during assertion. + (Constants.PROT*) :return: return value of the library call. :rtype: VISAStatus """ - flags = 0 - - error = self.interface.device_trigger(self.link, flags, self.lock_timeout, self.io_timeout) - - if error: - # TODO: Which status to return - raise Exception("error triggering: %d" % error) + error = self.interface.device_trigger(self.link, 0, self.lock_timeout, + self._io_timeout) - return SUCCESS + return VXI11_ERRORS_TO_VISA[error] def clear(self): """Clears a device. @@ -247,13 +269,10 @@ class TCPIPInstrSession(Session): :rtype: VISAStatus """ - flags = 0 - - error = self.interface.device_clear(self.link, flags, self.lock_timeout, self.io_timeout) + error = self.interface.device_clear(self.link, 0, self.lock_timeout, + self._io_timeout) - if error: - # TODO: Which status to return - raise Exception("error clearing: %d" % error) + return VXI11_ERRORS_TO_VISA[error] def read_stb(self): """Reads a status byte of the service request. @@ -261,28 +280,29 @@ class TCPIPInstrSession(Session): Corresponds to viReadSTB function of the VISA library. :return: Service request status byte, return value of the library call. - :rtype: int, VISAStatus + :rtype: int, :class:`pyvisa.constants.StatusCode` """ - flags = 0 - - error, stb = self.interface.device_read_stb(self.link, flags, self.lock_timeout, self.io_timeout) - if error: - # TODO: Which status to return - raise Exception("error reading status: %d" % error) + error, stb = self.interface.device_read_stb(self.link, 0, + self.lock_timeout, + self._io_timeout) - return stb, SUCCESS + return stb, VXI11_ERRORS_TO_VISA[error] def lock(self, lock_type, timeout, requested_key=None): """Establishes an access mode to the specified resources. Corresponds to viLock function of the VISA library. - :param lock_type: Specifies the type of lock requested, either Constants.EXCLUSIVE_LOCK or Constants.SHARED_LOCK. - :param timeout: Absolute time period (in milliseconds) that a resource waits to get unlocked by the - locking session before returning an error. - :param requested_key: This parameter is not used and should be set to VI_NULL when lockType is VI_EXCLUSIVE_LOCK. - :return: access_key that can then be passed to other sessions to share the lock, return value of the library call. + :param lock_type: Specifies the type of lock requested, either + Constants.EXCLUSIVE_LOCK or Constants.SHARED_LOCK. + :param timeout: Absolute time period (in milliseconds) that a resource + waits to get unlocked by the locking session before returning an + error. + :param requested_key: This parameter is not used and should be set to + VI_NULL when lockType is VI_EXCLUSIVE_LOCK. + :return: access_key that can then be passed to other sessions to share + the lock, return value of the library call. :rtype: str, VISAStatus """ @@ -291,9 +311,7 @@ class TCPIPInstrSession(Session): error = self.interface.device_lock(self.link, flags, self.lock_timeout) - if error: - # TODO: Which status to return - raise Exception("error locking: %d" % error) + return VXI11_ERRORS_TO_VISA[error] def unlock(self): """Relinquishes a lock for the specified resource. @@ -303,27 +321,39 @@ class TCPIPInstrSession(Session): :return: return value of the library call. :rtype: VISAStatus """ - flags = 0 - error = self.interface.device_unlock(self.link) - if error: - # TODO: Which message to return - raise Exception("error unlocking: %d" % error) + return VXI11_ERRORS_TO_VISA[error] + def _set_timeout(self, attribute, value): + """ Sets timeout calculated value from python way to VI_ way -@Session.register(constants.InterfaceType.tcpip, 'SOCKET') -class TCPIPSocketSession(Session): - """A TCPIP Session that uses the network standard library to do the low level communication. - """ + """ + if value == constants.VI_TMO_INFINITE: + self.timeout = None + self._io_timeout = 2**32-1 + elif value == constants.VI_TMO_IMMEDIATE: + self.timeout = 0 + self._io_timeout = 0 + else: + self.timeout = value / 1000.0 + self._io_timeout = int(self.timeout*1000) + return StatusCode.success - lock_timeout = 1000 - timeout = 1000 - max_recv_size = 4096 +@Session.register(constants.InterfaceType.tcpip, 'SOCKET') +class TCPIPSocketSession(Session): + """A TCPIP Session that uses the network standard library to do the low + level communication. - # This buffer is used to store the bytes that appeared after termination char - _pending_buffer = b'' + """ + # Details about implementation: + # On Windows, select is not interrupted by KeyboardInterrupt, to avoid + # blocking for very long time, we use a decreasing timeout in select. + # A minimum select timeout which prevents using too short select interval + # is also calculated and select timeout is not lower that that minimum + # timeout. The absolute minimum is 1 ms as a consequence. + # This is valid for connect and read operations @staticmethod def list_resources(): @@ -333,21 +363,65 @@ class TCPIPSocketSession(Session): def after_parsing(self): # TODO: board_number not handled - self.interface = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.interface.setblocking(0) + ret_status = self._connect() + if ret_status != StatusCode.success: + self.close() + raise Exception("could not connect: {0}".format(str(ret_status))) - try: - self.interface.connect_ex((self.parsed.host_address, int(self.parsed.port))) - except Exception as e: - raise Exception("could not create socket: %s" % e) + self.max_recv_size = 4096 + # This buffer is used to store the bytes that appeared after + # termination char + self._pending_buffer = bytearray() self.attrs[constants.VI_ATTR_TCPIP_ADDR] = self.parsed.host_address self.attrs[constants.VI_ATTR_TCPIP_PORT] = self.parsed.port self.attrs[constants.VI_ATTR_INTF_NUM] = self.parsed.board - - for name in 'TERMCHAR,TERMCHAR_EN'.split(','): + self.attrs[constants.VI_ATTR_TCPIP_NODELAY] = (self._get_tcpip_nodelay, + self._set_attribute) + self.attrs[constants.VI_ATTR_TCPIP_HOSTNAME] = '' + self.attrs[constants.VI_ATTR_TCPIP_KEEPALIVE] = \ + (self._get_tcpip_keepalive, self._set_tcpip_keepalive) + # to use default as ni visa driver (NI-VISA 15.0) + self.attrs[constants.VI_ATTR_SUPPRESS_END_EN] = True + + for name in ('TERMCHAR', 'TERMCHAR_EN'): attribute = getattr(constants, 'VI_ATTR_' + name) - self.attrs[attribute] = attributes.AttributesByID[attribute].default + self.attrs[attribute] =\ + attributes.AttributesByID[attribute].default + + def _connect(self): + timeout = self.open_timeout / 1000.0 if self.open_timeout else 10.0 + try: + self.interface = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.interface.setblocking(0) + self.interface.connect_ex((self.parsed.host_address, + int(self.parsed.port))) + except Exception as e: + raise Exception("could not connect: {0}".format(str(e))) + finally: + self.interface.setblocking(1) + + # minimum is in interval 100 - 500ms based on timeout + min_select_timeout = max(min(timeout/10.0, 0.5), 0.1) + # initial 'select_timout' is half of timeout or max 2 secs + # (max blocking time). min is from 'min_select_timeout' + select_timout = max(min(timeout/2.0, 2.0), min_select_timeout) + # time, when loop shall finish + finish_time = time.time() + timeout + while True: + # use select to wait for socket ready, max `select_timout` seconds + r, w, x = select.select([self.interface], [self.interface], [], + select_timout) + if self.interface in r or self.interface in w: + return StatusCode.success + + if time.time() >= finish_time: + # reached timeout + return StatusCode.error_timeout + + # `select_timout` decreased to 50% of previous or + # min_select_timeout + select_timout = max(select_timout / 2.0, min_select_timeout) def close(self): self.interface.close() @@ -367,47 +441,72 @@ class TCPIPSocketSession(Session): else: chunk_length = self.max_recv_size - end_char, _ = self.get_attribute(constants.VI_ATTR_TERMCHAR) - enabled, _ = self.get_attribute(constants.VI_ATTR_TERMCHAR_EN) - timeout, _ = self.get_attribute(constants.VI_ATTR_TMO_VALUE) - timeout /= 1000 - - end_byte = common.int_to_byte(end_char) if end_char else b'' + term_char, _ = self.get_attribute(constants.VI_ATTR_TERMCHAR) + term_byte = common.int_to_byte(term_char) if term_char else b'' + term_char_en, _ = self.get_attribute(constants.VI_ATTR_TERMCHAR_EN) + suppress_end_en, _ =\ + self.get_attribute(constants.VI_ATTR_SUPPRESS_END_EN) read_fun = self.interface.recv - now = start = time.time() - - out = self._pending_buffer - - if enabled and end_byte in out: - parts = out.split(end_byte) - self._pending_buffer = b''.join(parts[1:]) - return (out + parts[0] + end_byte, - constants.StatusCode.success_termination_character_read) - - while now - start <= timeout: - # use select to wait for read ready - select.select([self.interface], [], []) - last = read_fun(chunk_length) - - if not last: - time.sleep(.01) - now = time.time() - continue - - if enabled and end_byte in last: - parts = last.split(end_byte) - self._pending_buffer = b''.join(parts[1:]) - return (out + parts[0] + end_byte, - constants.StatusCode.success_termination_character_read) - - out += last - - if len(out) == count: - return out, constants.StatusCode.success_max_count_read - else: - return out, constants.StatusCode.error_timeout + # minimum is in interval 1 - 100ms based on timeout, 1sec if no timeout + # defined + min_select_timeout = (1 if self.timeout is None else + max(min(self.timeout / 100.0, 0.1), 0.001)) + # initial 'select_timout' is half of timeout or max 2 secs + # (max blocking time). min is from 'min_select_timeout' + select_timout = (2.0 if self.timeout is None else + max(min(self.timeout / 2.0, 2.0), min_select_timeout)) + # time, when loop shall finish, None means never ending story if no + # data arrives + finish_time = (None if self.timeout is None else + (time.time() + self.timeout)) + while True: + + # check, if we have any data received (from pending buffer or + # further reading) + if term_char_en and term_byte in self._pending_buffer: + term_byte_index = self._pending_buffer.index(term_byte) + 1 + if term_byte_index > count: + term_byte_index = count + status = StatusCode.success_max_count_read + else: + status = StatusCode.success_termination_character_read + out = bytes(self._pending_buffer[:term_byte_index]) + self._pending_buffer = self._pending_buffer[term_byte_index:] + return out, status + + if len(self._pending_buffer) >= count: + out = bytes(self._pending_buffer[:count]) + self._pending_buffer = self._pending_buffer[count:] + return out, StatusCode.success_max_count_read + + # use select to wait for read ready, max `select_timout` seconds + r, w, x = select.select([self.interface], [], [], select_timout) + + read_data = b'' + if self.interface in r: + read_data = read_fun(chunk_length) + self._pending_buffer.extend(read_data) + + if not read_data: + # can't read chunk or timeout + if self._pending_buffer and not suppress_end_en: + # we have some data without termchar but no further data + # expected + out = bytes(self._pending_buffer[:count]) + self._pending_buffer = self._pending_buffer[count:] + return out, StatusCode.succes + + if finish_time and time.time() >= finish_time: + # reached timeout + out = bytes(self._pending_buffer[:count]) + self._pending_buffer = self._pending_buffer[count:] + return out, StatusCode.error_timeout + + # `select_timout` decreased to 50% of previous or + # min_select_timeout + select_timout = max(select_timout / 2.0, min_select_timeout) def write(self, data): """Writes data to device or interface synchronously. @@ -416,7 +515,8 @@ class TCPIPSocketSession(Session): :param data: data to be written. :type data: str - :return: Number of bytes actually transferred, return value of the library call. + :return: Number of bytes actually transferred, return value of the + library call. :rtype: int, VISAStatus """ @@ -428,7 +528,7 @@ class TCPIPSocketSession(Session): while num > 0: - block = data[offset:min(offset+chunk_size, sz)] + block = data[offset:min(offset + chunk_size, sz)] try: # use select to wait for write ready @@ -443,7 +543,37 @@ class TCPIPSocketSession(Session): offset += size num -= size - return offset, SUCCESS + return offset, StatusCode.success + + def _get_tcpip_nodelay(self, attribute): + if self.interface: + value = self.interface.getsockopt(socket.IPPROTO_TCP, + socket.TCP_NODELAY) + return (constants.VI_TRUE if value == 1 else + constants.VI_FALSE, StatusCode.succes) + return 0, StatusCode.error_nonsupported_attribute + + def _set_tcpip_nodelay(self, attribute, attribute_state): + if self.interface: + self.interface.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, + 1 if attribute_state else 0) + return StatusCode.succes + return 0, StatusCode.error_nonsupported_attribute + + def _get_tcpip_keepalive(self, attribute): + if self.interface: + value = self.interface.getsockopt(socket.SOL_SOCKET, + socket.SO_KEEPALIVE) + return (constants.VI_TRUE if value == 1 else + constants.VI_FALSE, StatusCode.succes) + return 0, StatusCode.error_nonsupported_attribute + + def _set_tcpip_keepalive(self, attribute, attribute_state): + if self.interface: + self.interface.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, + 1 if attribute_state else 0) + return StatusCode.succes + return 0, StatusCode.error_nonsupported_attribute def _get_attribute(self, attribute): """Get the value for a given VISA attribute for this session. @@ -451,19 +581,10 @@ class TCPIPSocketSession(Session): Use to implement custom logic for attributes. :param attribute: Resource attribute for which the state query is made - :return: The state of the queried attribute for a specified resource, return value of the library call. + :return: The state of the queried attribute for a specified resource, + return value of the library call. :rtype: (unicode | str | list | int, VISAStatus) """ - - if attribute == constants.VI_ATTR_TCPIP_HOSTNAME: - raise NotImplementedError - - elif attribute == constants.VI_ATTR_TCPIP_KEEPALIVE: - raise NotImplementedError - - elif attribute == constants.VI_ATTR_TCPIP_NODELAY: - raise NotImplementedError - raise UnknownAttribute(attribute) def _set_attribute(self, attribute, attribute_state): @@ -471,8 +592,10 @@ class TCPIPSocketSession(Session): Corresponds to viSetAttribute function of the VISA library. - :param attribute: Attribute for which the state is to be modified. (Attributes.*) - :param attribute_state: The state of the attribute to be set for the specified object. + :param attribute: Attribute for which the state is to be modified. + (Attributes.*) + :param attribute_state: The state of the attribute to be set for the + specified object. :return: return value of the library call. :rtype: VISAStatus """ diff --git a/pyvisa-py/testsuite/__init__.py b/pyvisa-py/testsuite/__init__.py index 6157c88..27784b2 100644 --- a/pyvisa-py/testsuite/__init__.py +++ b/pyvisa-py/testsuite/__init__.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- -from __future__ import division, unicode_literals, print_function, absolute_import +from __future__ import (division, unicode_literals, print_function, + absolute_import) import os -from pyvisa.compat import unittest +import unittest def testsuite(): @@ -29,4 +30,3 @@ def run(): """ test_runner = unittest.TextTestRunner() return test_runner.run(testsuite()) - diff --git a/pyvisa-py/usb.py b/pyvisa-py/usb.py index 154879e..bf7cfc3 100644 --- a/pyvisa-py/usb.py +++ b/pyvisa-py/usb.py @@ -12,6 +12,7 @@ from __future__ import division, unicode_literals, print_function, absolute_import +import logging from pyvisa import constants, attributes from .sessions import Session, UnknownAttribute @@ -20,41 +21,34 @@ try: import usb from .protocols import usbtmc, usbutil, usbraw except ImportError as e: - Session.register_unavailable(constants.InterfaceType.usb, 'INSTR', - 'Please install PyUSB to use this resource type.\n%s' % e) - - Session.register_unavailable(constants.InterfaceType.usb, 'RAW', - 'Please install PyUSB to use this resource type.\n%s' % e) + msg = 'Please install PyUSB to use this resource type.\n%s' + Session.register_unavailable(constants.InterfaceType.usb, + 'INSTR', msg % e) + Session.register_unavailable(constants.InterfaceType.usb, + 'RAW', msg % e) raise try: _ = usb.core.find() -except ValueError as e: +except Exception as e: msg = 'PyUSB does not seem to be properly installed.\n' \ - 'Please refer to PyUSB documentation and ' \ - 'install a suitable backend like ' \ - 'libusb 0.1, libusb 1.0, libusbx, ' \ - 'libusb-win32 or OpenUSB\%s' % e - + 'Please refer to PyUSB documentation and \n' \ + 'install a suitable backend like \n' \ + 'libusb 0.1, libusb 1.0, libusbx, \n' \ + 'libusb-win32 or OpenUSB.\n%s' % e Session.register_unavailable(constants.InterfaceType.usb, 'INSTR', msg) - Session.register_unavailable(constants.InterfaceType.usb, 'RAW', msg) - raise -from . import common - StatusCode = constants.StatusCode -SUCCESS = StatusCode.success + class USBSession(Session): """Base class for drivers that communicate with usb devices via usb port using pyUSB """ - timeout = 2000 - @staticmethod def list_resources(): """Return list of resources for this type of USB device""" @@ -70,11 +64,26 @@ class USBSession(Session): try: # noinspection PyProtectedMember backend = usb.core.find()._ctx.backend.__class__.__module__.split('.')[-1] - except: + except Exception: backend = 'N/A' return 'via PyUSB (%s). Backend: %s' % (ver, backend) + def _get_timeout(self, attribute): + if self.interface: + if self.interface.timeout == 2**32-1: + self.timeout = None + else: + self.timeout = self.interface.timeout / 1000 + return super(USBSession, self)._get_timeout(attribute) + + def _set_timeout(self, attribute, value): + status = super(USBSession, self)._set_timeout(attribute, value) + timeout = int(self.timeout*1000) if self.timeout else 2**32-1 + if self.interface: + self.interface.timeout = timeout + return status + def read(self, count): """Reads data from device or interface synchronously. @@ -93,9 +102,9 @@ class USBSession(Session): term_char, _ = self.get_attribute(constants.VI_ATTR_TERMCHAR) term_char_en, _ = self.get_attribute(constants.VI_ATTR_TERMCHAR_EN) - return self._read(lambda: self.interface.read(1), + return self._read(lambda: self.interface.read(count), count, - lambda current: False, + lambda current: True, # USB always returns a complete message supress_end_en, term_char, term_char_en, @@ -116,7 +125,7 @@ class USBSession(Session): count = self.interface.write(data) - return count, SUCCESS + return count, StatusCode.success def close(self): self.interface.close() @@ -146,6 +155,7 @@ class USBSession(Session): raise UnknownAttribute(attribute) + @Session.register(constants.InterfaceType.usb, 'INSTR') class USBInstrSession(USBSession): """Base class for drivers that communicate with instruments @@ -158,15 +168,30 @@ class USBInstrSession(USBSession): fmt = 'USB%(board)s::%(manufacturer_id)s::%(model_code)s::' \ '%(serial_number)s::%(usb_interface_number)s::INSTR' for dev in usbtmc.find_tmc_devices(): - intfc = usbutil.find_interfaces(dev, bInterfaceClass=0xfe, bInterfaceSubClass=3) + intfc = usbutil.find_interfaces(dev, bInterfaceClass=0xfe, + bInterfaceSubClass=3) try: intfc = intfc[0].index except (IndexError, AttributeError): intfc = 0 + + try: + serial = dev.serial_number + except (NotImplementedError, ValueError): + logger = logging.getLogger(__name__) + msg = ('Found a device whose serial number cannot be read.' + ' The partial VISA resource name is: ' + fmt) + logger.warning(msg, dict(board=0, + manufacturer_id=dev.idVendor, + model_code=dev.idProduct, + serial_number='???', + usb_interface_number=intfc)) + continue + out.append(fmt % dict(board=0, manufacturer_id=dev.idVendor, model_code=dev.idProduct, - serial_number=dev.serial_number, + serial_number=serial, usb_interface_number=intfc)) return out @@ -175,7 +200,7 @@ class USBInstrSession(USBSession): int(self.parsed.model_code, 0), self.parsed.serial_number) - for name in 'SEND_END_EN,TERMCHAR,TERMCHAR_EN'.split(','): + for name in ('SEND_END_EN', 'TERMCHAR', 'TERMCHAR_EN'): attribute = getattr(constants, 'VI_ATTR_' + name) self.attrs[attribute] = attributes.AttributesByID[attribute].default @@ -197,10 +222,24 @@ class USBRawSession(USBSession): intfc = intfc[0].index except (IndexError, AttributeError): intfc = 0 + + try: + serial = dev.serial_number + except (NotImplementedError, ValueError): + logger = logging.getLogger(__name__) + msg = ('Found a device whose serial number cannot be read.' + ' The partial VISA resource name is: ' + fmt) + logger.warning(msg, dict(board=0, + manufacturer_id=dev.idVendor, + model_code=dev.idProduct, + serial_number='???', + usb_interface_number=intfc)) + continue + out.append(fmt % dict(board=0, manufacturer_id=dev.idVendor, model_code=dev.idProduct, - serial_number=dev.serial_number, + serial_number=serial, usb_interface_number=intfc)) return out @@ -209,6 +248,6 @@ class USBRawSession(USBSession): int(self.parsed.model_code, 0), self.parsed.serial_number) - for name in 'SEND_END_EN,TERMCHAR,TERMCHAR_EN'.split(','): + for name in ('SEND_END_EN', 'TERMCHAR', 'TERMCHAR_EN'): attribute = getattr(constants, 'VI_ATTR_' + name) self.attrs[attribute] = attributes.AttributesByID[attribute].default @@ -1,12 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - -try: - import sys - reload(sys).setdefaultencoding("UTF-8") -except: - pass - +import sys try: from setuptools import setup @@ -15,11 +9,9 @@ except ImportError: sys.exit(1) -import codecs - - def read(filename): - return codecs.open(filename, encoding='utf-8').read() + with open(filename, 'rb') as f: + return f.read().decode('utf8') long_description = '\n\n'.join([read('README'), @@ -30,18 +22,16 @@ __doc__ = long_description requirements = ['pyvisa>=1.8'] -if sys.version_info < (2, 7): - requirements.append('importlib') setup(name='PyVISA-py', description='Python VISA bindings for GPIB, RS232, and USB instruments', - version='0.2', + version='0.3.0', long_description=long_description, author='Hernan E. Grecco', author_email='hernan.grecco@gmail.com', maintainer='Hernan E. Grecco', maintainer_email='hernan.grecco@gmail.com', - url='https://github.com/hgrecco/pyvisa-py', + url='https://github.com/pyvisa/pyvisa-py', test_suite='pyvisa-py.testsuite.testsuite', keywords='Remote VISA GPIB USB serial RS232 measurement acquisition', license='MIT License', @@ -57,11 +47,11 @@ setup(name='PyVISA-py', 'Programming Language :: Python', 'Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator', 'Topic :: Software Development :: Libraries :: Python Modules', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ], packages=['pyvisa-py', 'pyvisa-py.protocols', |