diff options
42 files changed, 1133 insertions, 306 deletions
diff --git a/.travis.yml b/.travis.yml index 28f009c..291df6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,22 @@ language: python +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 + python: - - "2.6" - "2.7" - - "3.2" - - "3.3" - "3.4" + - "3.5" + - "3.6" install: - - if [ $TRAVIS_PYTHON_VERSION == '2.6' ]; then pip install unittest2; fi + - if [ $TRAVIS_PYTHON_VERSION == '2.7' ] || [ $TRAVIS_PYTHON_VERSION == '3.6' ]; then pip install numpy; fi - pip install coverage coveralls script: @@ -2,6 +2,31 @@ PyVISA Changelog ================ +1.9 (2017-02-25) +---------------- + +- Drop support for Python 2.6, 3.2 and 3.3 PR #300 +- add the missing read_binary_values and read_ascii_values (PR #301) +- deprecate old methods in MessageBased (ask, read_values, query_values, + write_values, ask_delay) (PR #301) +- add support for hp headers in binary data (PR #301) +- fix encoding issue in write_ascii_values (PR #301) +- use import to load backend rather than pkgutil.iter_modules. This allows + PyVISA to support PyInstaller PR #307 +- improvements to the visa shell: attributes type conversion (PR #299), + termchar command (PR #285), timeout command (PR #284), + support for non-default backend (PR #283), console script pyvisa-shell + (PR #286) +- improve speed for large data transfer by using bytearray instead of bytes + (PR #282) +- make Resource a context manager closing it. (PR #255) +- add 64 bits version of registry based functions (PR #278) +- make exceptions pickable (PR #249) +- add resource_name to the output of parse_resource_extended (PR #238) +- fix wait_on_event behavior in case of timeout (PR #234) +- allow selecting the backend using the PYVISA_LIBRARY env var (PR #195) + + 1.8 (2015-08-24) ---------------- @@ -98,7 +123,7 @@ PyVISA Changelog - Better debug info for binary libraries. - Fixed exceptions formatting (thanks Matthew94) - + 1.6 (2014-09-28) @@ -44,7 +44,7 @@ Requirements ------------ - VISA (tested with NI-VISA 3.2, WinXP, from www.ni.com/visa) -- Python (tested with 2.6 and 3.2+) +- Python (tested with 2.7 and 3.4+) Installation diff --git a/debian/.git-dpm b/debian/.git-dpm deleted file mode 100644 index 67f3f17..0000000 --- a/debian/.git-dpm +++ /dev/null @@ -1,11 +0,0 @@ -# see git-dpm(1) from git-dpm package -a5c3cbf9b15ec3f61547dc5ba6d1d3c427c29f70 -a5c3cbf9b15ec3f61547dc5ba6d1d3c427c29f70 -a5c3cbf9b15ec3f61547dc5ba6d1d3c427c29f70 -a5c3cbf9b15ec3f61547dc5ba6d1d3c427c29f70 -pyvisa_1.8.orig.tar.gz -9f38c897d303ea49a546b58d6b356799fbcd2e84 -423435 -debianTag="debian/%e%v" -patchedTag="patched/%e%v" -upstreamTag="upstream/%e%u" diff --git a/debian/changelog b/debian/changelog index faed03f..eec2cbe 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,27 @@ +pyvisa (1.9.0-1) unstable; urgency=medium + + * New upstream release + + [ Ondřej Nový ] + * debian/control: Set Vcs-* to salsa.debian.org + * debian/copyright: Use https protocol in Format field + * debian/control: Deprecating priority extra as per policy 4.0.1 + * debian/changelog: Remove trailing whitespaces + * debian/control: Remove ancient X-Python3-Version field + + [ Ruben Undheim ] + * Updated homepage URL, watch URL and source url in debian/copyright + * debian/copyright: Updates according to changes in new release + * debian/compat: compat level set to 11 + * debian/control: + - debhelper >= 11 + - Standards version 4.2.0 + * Deleted debian/.git-dpm + * debian/patches/0001-fix-build.patch: + - Fix an issue popping up when building on latest sid + + -- Ruben Undheim <ruben.undheim@gmail.com> Sun, 05 Aug 2018 21:12:47 +0200 + pyvisa (1.8-4) unstable; urgency=low * Moved python-pyvisa-py back from Depends to Recommends. (Closes: #836963) @@ -34,7 +58,7 @@ pyvisa (1.8-1) unstable; urgency=low pyvisa (1.7-1) unstable; urgency=medium - * New upstream release + * New upstream release * Updated d/copyright with info about new files. * d/control: - transitional package pyvisa moved to Section "oldlibs" and description diff --git a/debian/compat b/debian/compat index ec63514..b4de394 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -9 +11 diff --git a/debian/control b/debian/control index 79f40fe..fa18e45 100644 --- a/debian/control +++ b/debian/control @@ -3,23 +3,21 @@ Section: python Priority: optional Maintainer: Debian Python Modules Team <python-modules-team@lists.alioth.debian.org> Uploaders: Ruben Undheim <ruben.undheim@gmail.com> -Build-Depends: debhelper (>= 9), +Build-Depends: debhelper (>= 11), dh-python, python-all, python-setuptools, python3-all, python3-setuptools -Standards-Version: 3.9.8 +Standards-Version: 4.2.0 X-Python-Version: all -X-Python3-Version: >= 3.1 -Vcs-Browser: https://anonscm.debian.org/cgit/python-modules/packages/pyvisa.git -Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/pyvisa.git -Homepage: http://pyvisa.sourceforge.net/ +Vcs-Browser: https://salsa.debian.org/python-team/modules/pyvisa +Vcs-Git: https://salsa.debian.org/python-team/modules/pyvisa.git +Homepage: https://pyvisa.readthedocs.io/ Package: pyvisa Depends: python-pyvisa, ${misc:Depends} Architecture: all -Priority: extra Section: oldlibs Description: Transitional dummy package for python-pyvisa This is a transitional dummy package. It can safely be removed. diff --git a/debian/copyright b/debian/copyright index 0cae160..593bb84 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,9 +1,9 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: pyvisa -Source: https://github.com/hgrecco/pyvisa +Source: https://github.com/pyvisa/pyvisa Files: * -Copyright: 2005-2014 PyVISA Authors and contributors +Copyright: 2005-2018 PyVISA Authors and contributors 2005, 2006, 2007, 2008 Torsten Bronger <bronger@physik.rwth-aachen.de> 2005, 2006, 2007, 2008 Gregor Thalhammer <gth@users.sourceforge.net> 2012, 2013 Florian Bauer @@ -21,8 +21,7 @@ Files: pyvisa/thirdparty/prettytable.py Copyright: 2009-2013 Luke Maurits <luke@maurits.id.au> License: BSD-3-clause -Files: pyvisa/compat/check_output.py - pyvisa/compat/nullhandler.py +Files: pyvisa/compat/nullhandler.py pyvisa/compat/struct.py Copyright: 2013, 2015 PSF License: PSF @@ -42,7 +41,7 @@ License: BSD-3-clause Files: debian/* Copyright: 2013 Simon Richter <sjr@debian.org> - 2015 Ruben Undheim <ruben.undheim@gmail.com> + 2015-2016,2018 Ruben Undheim <ruben.undheim@gmail.com> License: GPL-2+ diff --git a/debian/patches/0001-fix-build.patch b/debian/patches/0001-fix-build.patch new file mode 100644 index 0000000..562edcc --- /dev/null +++ b/debian/patches/0001-fix-build.patch @@ -0,0 +1,31 @@ +From: Ruben Undheim <ruben.undheim@gmail.com> +Date: Sun, 5 Aug 2018 19:28:21 +0000 +Subject: Fix build + +--- + setup.py | 9 +++++---- + 1 file changed, 5 insertions(+), 4 deletions(-) + +diff --git a/setup.py b/setup.py +index a3d05a8..43c0732 100644 +--- a/setup.py ++++ b/setup.py +@@ -16,13 +16,14 @@ except ImportError: + + + def read(filename): +- with open(filename, 'r') as f: ++ with open(filename, 'rb') as f: + return f.read() + ++a2 = read('AUTHORS').decode('utf-8') ++a3 = read('CHANGES').decode('utf-8') ++a1 = read('README').decode('utf-8') + +-long_description = '\n\n'.join([read('README'), +- read('AUTHORS'), +- read('CHANGES')]) ++long_description = '\n\n'.join([a1,a2,a3]) + + __doc__ = long_description + diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..83704e9 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +0001-fix-build.patch diff --git a/debian/watch b/debian/watch index c0f5f5a..c427358 100644 --- a/debian/watch +++ b/debian/watch @@ -1,2 +1,2 @@ version=3 -https://github.com/hgrecco/pyvisa/releases /hgrecco/pyvisa/archive/(\d\S+)\.tar\.(?:bz2|gz|xz) +https://github.com/pyvisa/pyvisa/releases /pyvisa/pyvisa/archive/(\d\S+)\.tar\.(?:bz2|gz|xz) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 92ebbc4..7ccc188 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -13,8 +13,8 @@ You are currently looking at the documentation of version {{ version }}. <h3>Useful Links</h3> <ul> <li><a href="https://pypi.python.org/pypi/pyvisa/">PyVISA @ PyPI</a></li> - <li><a href="https://github.com/hgrecco/pyvisa">Code in GitHub</a></li> - <li><a href="https://github.com/hgrecco/pyvisa/issues">Issue Tracker</a></li> + <li><a href="https://github.com/pyvisa/pyvisa">Code in GitHub</a></li> + <li><a href="https://github.com/pyvisa/pyvisa/issues">Issue Tracker</a></li> </ul> diff --git a/docs/_templates/sidebarlogo.html b/docs/_templates/sidebarlogo.html index f220931..e42afdf 100644 --- a/docs/_templates/sidebarlogo.html +++ b/docs/_templates/sidebarlogo.html @@ -17,7 +17,7 @@ You are currently looking at the documentation of version {{ version }}. <h3>Useful Links</h3> <ul> <li><a href="https://pypi.python.org/pypi/pyvisa/">PyVISA @ PyPI</a></li> - <li><a href="https://github.com/hgrecco/pyvisa">Code in GitHub</a></li> - <li><a href="https://github.com/hgrecco/pyvisa/issues">Issue Tracker</a></li> + <li><a href="https://github.com/pyvisa/pyvisa">Code in GitHub</a></li> + <li><a href="https://github.com/pyvisa/pyvisa/issues">Issue Tracker</a></li> </ul> diff --git a/docs/backends.rst b/docs/backends.rst index 1aded42..53a7601 100644 --- a/docs/backends.rst +++ b/docs/backends.rst @@ -65,6 +65,13 @@ What does a minimum backend looks like? Quite simple:: Additionally you can provide a staticmethod named get_debug_info` that should return a dictionary of debug information which is printed when you call ``python -m visa info`` +.. note:: + + Your backend name should not end by ``-script`` or it will be discarded. + This is because any script generated by setuptools containing the name + pyvisa will be named ``pyvisa-*-script`` and they are obviously not backends. + Examples are the ``pyvisa-shell`` and ``pyvisa-info`` scripts. + An important aspect of developing a backend is knowing which VisaLibraryBase method to implement and what API to expose. diff --git a/docs/conf.py b/docs/conf.py index 1a1d32d..dd66e73 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,10 +44,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/configuring.rst b/docs/configuring.rst index ddba398..867722e 100644 --- a/docs/configuring.rst +++ b/docs/configuring.rst @@ -1,5 +1,17 @@ .. _configuring: +Configuring the backend +============================ +Currently there are two backends available: The one included in pyvisa which +uses the NI library. This is used by default and the configuration is described +in the next chapter. +And then there is pyvia-py a pure python implementation of the VISA libary. +It can be selected by passing a parameter to the ResourceManager: + + >>> visa.ResourceManager('@py') + +Alternativly it can also be selected by setting the environment variable +PYVISA_LIBRARY. It takes the same values as the ResourceManager constructor. Configuring the NI backend ========================== @@ -58,4 +70,4 @@ solutions to common problem as well as useful debugging techniques. If everythin feel free to open an issue in our `issue tracker`_ .. _`home directory`: http://en.wikipedia.org/wiki/Home_directory -.. _`issue tracker`: https://github.com/hgrecco/pyvisa/issues +.. _`issue tracker`: https://github.com/pyvisa/pyvisa/issues diff --git a/docs/contributing.rst b/docs/contributing.rst index 0291f11..13a104a 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -25,15 +25,15 @@ To contribute fixes, code or documentation to PyVISA, send us a patch, or fork P You can also get the code from PyPI_ or GitHub_. You can either clone the public repository:: - $ git clone git://github.com/hgrecco/pyvisa.git + $ git clone git://github.com/pyvisa/pyvisa.git Download the tarball:: - $ curl -OL https://github.com/hgrecco/pyvisa/tarball/master + $ curl -OL https://github.com/pyvisa/pyvisa/tarball/master Or, download the zipball:: - $ curl -OL https://github.com/hgrecco/pyvisa/zipball/master + $ curl -OL https://github.com/pyvisa/pyvisa/zipball/master Once you have a copy of the source, you can embed it in your Python package, or install it into your site-packages easily:: @@ -70,7 +70,7 @@ it. You can use any of the existing backends as a template or start a thread in .. _`Anaconda CE`: https://store.continuum.io/cshop/anaconda .. _PyPI: https://pypi.python.org/pypi/PyVISA .. _`National Instruments's VISA`: http://ni.com/visa/ -.. _github: http://github.com/hgrecco/pyvisa -.. _`issue tracker`: https://github.com/hgrecco/pyvisa/issues +.. _github: http://github.com/pyvisa/pyvisa +.. _`issue tracker`: https://github.com/pyvisa/pyvisa/issues diff --git a/docs/faq.rst b/docs/faq.rst index b07f6d6..24df6df 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -169,6 +169,6 @@ Where can I get more information about VISA? http://digital.ni.com/manuals.nsf/websearch/266526277DFF74F786256ADC0065C50C -.. _`AUTHORS`: https://github.com/hgrecco/pyvisa/blob/master/AUTHORS -.. _`Issue Tracker`: https://github.com/hgrecco/pyvisa/issues +.. _`AUTHORS`: https://github.com/pyvisa/pyvisa/blob/master/AUTHORS +.. _`Issue Tracker`: https://github.com/pyvisa/pyvisa/issues .. _`virtual environment`: http://www.virtualenv.org/en/latest/ diff --git a/docs/getting.rst b/docs/getting.rst index d6e85af..fda7fb7 100644 --- a/docs/getting.rst +++ b/docs/getting.rst @@ -3,7 +3,7 @@ Installation ============ -PyVISA is a frontend to the VISA library. It runs on Python 2.6+ and 3.2+. +PyVISA is a frontend to the VISA library. It runs on Python 2.7 and 3.4+. You can install it using pip_:: @@ -44,7 +44,7 @@ Using the development version You can install the latest development version (at your own risk) directly form GitHub_:: - $ pip install -U https://github.com/hgrecco/pyvisa/zipball/master + $ pip install -U https://github.com/pyvisa/pyvisa/zipball/master .. note:: If you have an old system installation of Python and you don't want to @@ -57,6 +57,6 @@ You can install the latest development version (at your own risk) directly form .. _pip: http://www.pip-installer.org/ .. _`Anaconda CE`: https://store.continuum.io/cshop/anaconda .. _PyPI: https://pypi.python.org/pypi/PyVISA -.. _GitHub: https://github.com/hgrecco/pyvisa +.. _GitHub: https://github.com/pyvisa/pyvisa .. _`National Instruments's VISA`: http://ni.com/visa/ -.. _`issue tracker`: https://github.com/hgrecco/pyvisa/issues +.. _`issue tracker`: https://github.com/pyvisa/pyvisa/issues diff --git a/docs/getting_nivisa.rst b/docs/getting_nivisa.rst index 8a3c2e9..393f2eb 100644 --- a/docs/getting_nivisa.rst +++ b/docs/getting_nivisa.rst @@ -11,7 +11,7 @@ PyVISA includes a debugging command to help you troubleshoot this (and other thi According to National Instruments, NI VISA **5.4.1** is available for: -.. note:: NI-VISA is not available for your system, take a look at the :ref:`faq`. +.. note:: If NI-VISA is not available for your system, take a look at the :ref:`faq`. Mac OS X diff --git a/docs/resources.rst b/docs/resources.rst index 93a0211..031a65b 100644 --- a/docs/resources.rst +++ b/docs/resources.rst @@ -10,7 +10,7 @@ attributes an methods that are available by the underlying device. You do not create this objects directly but they are returned by the :meth:`pyvisa.highlevel.ResourceManager.open_resource` method of a :class:`pyvisa.highlevel.ResourceManager`. In general terms, there -are two main groups derived from :class:`pyvisa.resources.Resource`, :class:`pyvisa.resources.RegisterBasedResource` and :class:`pyvisa.resources.RegisterBasedResource`. +are two main groups derived from :class:`pyvisa.resources.Resource`, :class:`pyvisa.resources.RegisterBasedResource` and :class:`pyvisa.resources.MessageBasedResource`. .. note:: The resource Python class to use is selected automatically from the resource name. However, you can force a Resource Python class: diff --git a/docs/rvalues.rst b/docs/rvalues.rst index 383585c..aecb6a6 100644 --- a/docs/rvalues.rst +++ b/docs/rvalues.rst @@ -77,6 +77,11 @@ If you have doubles `d` in big endian the call will be:: You can also specify the output container type, just as it was shown before. +By default, PyVISA will assume that the data block is formatted according to +the IEEE convention. If your instrument uses HP data block you can pass +``header_fmt='hp'`` to ``read_binary_values``. If your instrument does not use +any header for the data simply ``header_fmt='empty'``. + Writing ASCII values -------------------- @@ -178,4 +183,12 @@ In those cases, you need to get the data:: and then you need to implement the logic to parse it. +Alternatively if the `read_raw` call fails you can try to read just a few bytes +using:: + + >>> inst.write('CURV?') + >>> data = inst.read_bytes(1) +If this call fails it may mean that your instrument did not answer, either +because it needs more time or because your first instruction was not +understood. diff --git a/docs/shell.rst b/docs/shell.rst index 0943d90..70d51e7 100644 --- a/docs/shell.rst +++ b/docs/shell.rst @@ -8,6 +8,10 @@ interface to interact with instruments. You can invoke it from the command-line: python -m visa shell +or:: + + pyvisa-shell + that will show something the following prompt:: Welcome to the VISA shell. Type help or ? to list commands. @@ -20,7 +24,7 @@ At any time, you can type ``?`` or ``help`` to get a list of valid commands:: Documented commands (type help <topic>): ======================================== - EOF attr close exit help list open query read write + EOF attr close exit help list open query read timeout write (visa) help list List all connected resources. @@ -45,6 +49,16 @@ Let's open comport 1:: (open) query *IDN? Some Instrument, Some Company. +You can print timeout that is set for query/read operation:: + + (open) timeout + Timeout: 2000ms + +Then also to change the timeout for example to 1500ms (1.5 sec):: + + (open) timeout 1500 + Done + We can also get a list of all visa attributes:: +-----------------------------+------------+----------------------------+-------------------------------------+ @@ -100,11 +114,97 @@ We can also get a list of all visa attributes:: | VI_ATTR_WR_BUF_SIZE | 1073676334 | | 4096 | +-----------------------------+------------+----------------------------+-------------------------------------+ + +To simplify the handling of VI_ATTR_TERMCHAR and VI_ATTR_TERMCHAR_EN, the command 'termchar' can be used. +If only one character provided, it sets both read and write termination character to the same character. +If two characters are provided, it sets read and write termination characters independently. + +To setup termchar to '\r' (CR or ascii code 10):: + + (open) termchar CR + Done + +To read what termchar is defined:: + + (open) termchar + Termchar read: CR write: CR + +To setup read termchar to '\n' and write termchar to '\r\n\':: + + (open) termchar LF CRLF + Done + +Supported termchar values are: CR ('\r'), LF ('\n'), CRLF ('\r\n') , NUL ('\0'), None. +None is used to disable termchar. + + Finally, you can close the device:: (open) close +PyVisa Shell Backends +===================== + +Based on available backend (see bellow for ``info`` command), it is possible to switch shell to use non-default backend via +``-b BACKEND`` or ``--backend BACKEND``. + +You can invoke:: + + python -m visa -b sim shell + +or:: + + pyvisa-shell -b sim + +to use python-sim as backend instead of ni backend. +This can be used for example for testing of python-sim configuration. + +You can invoke:: + + python -m visa -b py shell + +or:: + + pyvisa-shell -b py + +uses python-py as backend instead of ni backend, for situation when ni not installed. + + +PyVisa Info +=========== + +You can invoke it from the command-line:: + + python -m visa info + +or:: + + pyvisa-info + +that will print information to diagnose PyVISA, info about Machine, Python, backends, etc :: + + Machine Details: + Platform ID: Windows + Processor: Intel64 Family 6 + ... + PyVISA Version: ... + + Backends: + ni: + Version: 1.8 (bundled with PyVISA) + ... + py: + Version: 0.2 + ... + sim: + Version: 0.3 + Spec version: 1.1 + + +Summary +======= + Cool, right? It will be great to have a GUI similar to NI-MAX, but we leave that to be developed outside PyVISA. Want to help? Let us know! diff --git a/pyvisa/compat/__init__.py b/pyvisa/compat/__init__.py index a3dc8fb..abd0f78 100644 --- a/pyvisa/compat/__init__.py +++ b/pyvisa/compat/__init__.py @@ -9,9 +9,12 @@ :license: BSD, 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 + + PYTHON3 = sys.version >= '3' if PYTHON3: @@ -23,6 +26,10 @@ if PYTHON3: integer_types = (int, ) input = input + + int_to_bytes = int.to_bytes + int_from_bytes = int.from_bytes + else: string_types = basestring @@ -35,14 +42,85 @@ else: input = raw_input -if sys.version_info < (2, 7): - try: - # noinspection PyPackageRequirements - import unittest2 as unittest - except ImportError: - raise Exception("Testing PyVISA in Python 2.6 requires package 'unittest2'") -else: - import unittest + # The 2 following function implementation extracted from the python-future + # project + import collections + + def int_to_bytes(integer, length, byteorder, signed=False): + """ + Return an array of bytes representing an integer. + The integer is represented using length bytes. An OverflowError is + raised if the integer is not representable with the given number of + bytes. + The byteorder argument determines the byte order used to represent the + integer. If byteorder is 'big', the most significant byte is at the + beginning of the byte array. If byteorder is 'little', the most + significant byte is at the end of the byte array. To request the + native byte order of the host system, use `sys.byteorder' as the byte + order value. + The signed keyword-only argument determines whether two's complement is + used to represent the integer. If signed is False and a negative + integer is given, an OverflowError is raised. + """ + if length < 0: + raise ValueError("length argument must be non-negative") + if length == 0 and integer == 0: + return bytes() + if signed and integer < 0: + bits = length * 8 + num = (2**bits) + integer + if num <= 0: + raise OverflowError("int too smal to convert") + else: + if integer < 0: + raise OverflowError("can't convert negative int to unsigned") + num = integer + if byteorder not in ('little', 'big'): + raise ValueError("byteorder must be either 'little' or 'big'") + h = b'%x' % num + s = bytes((b'0'*(len(h) % 2) + h).zfill(length*2).decode('hex')) + if signed: + high_set = s[0] & 0x80 + if integer > 0 and high_set: + raise OverflowError("int too big to convert") + if integer < 0 and not high_set: + raise OverflowError("int too small to convert") + if len(s) > length: + raise OverflowError("int too big to convert") + return s if byteorder == 'big' else s[::-1] + + def int_from_bytes(mybytes, byteorder='big', signed=False): + """ + Return the integer represented by the given array of bytes. + The mybytes argument must either support the buffer protocol or be an + iterable object producing bytes. Bytes and bytearray are examples of + built-in objects that support the buffer protocol. + The byteorder argument determines the byte order used to represent the + integer. If byteorder is 'big', the most significant byte is at the + beginning of the byte array. If byteorder is 'little', the most + significant byte is at the end of the byte array. To request the + native byte order of the host system, use `sys.byteorder' as the byte + order value. + The signed keyword-only argument indicates whether two's complement is + used to represent the integer. + """ + if byteorder not in ('little', 'big'): + raise ValueError("byteorder must be either 'little' or 'big'") + if isinstance(mybytes, unicode): + raise TypeError("cannot convert unicode objects to bytes") + # mybytes can also be passed as a sequence of integers on Py3. + # Test for this: + elif isinstance(mybytes, collections.Iterable): + mybytes = bytes(mybytes) + b = mybytes if byteorder == 'big' else mybytes[::-1] + if len(b) == 0: + b = b'\x00' + # The encode() method has been disabled by newbytes, but Py2's + # str has it: + num = int(b.encode('hex'), 16) + if signed and (b[0] & 0x80): + num = num - (2 ** (len(b)*8)) + return num try: from collections import OrderedDict @@ -54,11 +132,6 @@ try: except ImportError: from .nullhandler import NullHandler -try: - from subprocess import check_output -except ImportError: - from .check_output import check_output - def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" diff --git a/pyvisa/compat/check_output.py b/pyvisa/compat/check_output.py deleted file mode 100644 index 4d57e8b..0000000 --- a/pyvisa/compat/check_output.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pyvisa.compat.check_output - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Taken from the Python 2.7 source code. - - :copyright: 2013, PSF - :license: PSF License -""" - -from __future__ import division, unicode_literals, print_function, absolute_import - -import subprocess - - -def check_output(*popenargs, **kwargs): - r"""Run command with arguments and return its output as a byte string. - - Backported from Python 2.7 as it's implemented as pure python on stdlib. - - >>> check_output(['/usr/bin/python', '--version']) - Python 2.6.2 - """ - process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) - output, unused_err = process.communicate() - retcode = process.poll() - if retcode: - cmd = kwargs.get("args") - if cmd is None: - cmd = popenargs[0] - error = subprocess.CalledProcessError(retcode, cmd) - error.output = output - raise error - return output diff --git a/pyvisa/compat/struct.py b/pyvisa/compat/struct.py index 154ddbf..2d0e429 100644 --- a/pyvisa/compat/struct.py +++ b/pyvisa/compat/struct.py @@ -9,7 +9,8 @@ :license: PSF License """ -from __future__ import division, unicode_literals, print_function, absolute_import +from __future__ import (division, unicode_literals, print_function, + absolute_import) import sys import struct diff --git a/pyvisa/constants.py b/pyvisa/constants.py index 2172306..c2e907f 100644 --- a/pyvisa/constants.py +++ b/pyvisa/constants.py @@ -688,7 +688,7 @@ class InterfaceType(enum.IntEnum): class AddressState(enum.IntEnum): - unaddressed =VI_GPIB_UNADDRESSED + unaddressed = VI_GPIB_UNADDRESSED talker = VI_GPIB_TALKER listenr = VI_GPIB_LISTENER diff --git a/pyvisa/ctwrapper/functions.py b/pyvisa/ctwrapper/functions.py index 8208067..472d573 100644 --- a/pyvisa/ctwrapper/functions.py +++ b/pyvisa/ctwrapper/functions.py @@ -40,7 +40,9 @@ visa_functions = [ "terminate", "uninstall_handler", "unlock", "unmap_address", "unmap_trigger", "usb_control_in", "usb_control_out", "vxi_command_query", "wait_on_event", - "write", "write_asynchronously", "write_from_file"] + "write", "write_asynchronously", "write_from_file", + "in_64", "move_in_64", "out_64", "move_out_64", "poke_64", + "peek_64"] __all__ = ["visa_functions", 'set_signatures'] + visa_functions diff --git a/pyvisa/ctwrapper/highlevel.py b/pyvisa/ctwrapper/highlevel.py index 1399046..8269c41 100644 --- a/pyvisa/ctwrapper/highlevel.py +++ b/pyvisa/ctwrapper/highlevel.py @@ -72,10 +72,10 @@ class NIVisaLibrary(highlevel.VisaLibraryBase): user_lib = read_user_library_path() tmp = [find_library(library_path) - for library_path in ('visa', 'visa32', 'visa32.dll')] + for library_path in ('visa', 'visa32', 'visa32.dll', 'visa64', 'visa64.dll')] tmp = [LibraryPath(library_path) - for library_path in tmp + for library_path in set(tmp) if library_path is not None] logger.debug('Automatically found library files: %s' % tmp) diff --git a/pyvisa/errors.py b/pyvisa/errors.py index 94ec7d1..b29534a 100644 --- a/pyvisa/errors.py +++ b/pyvisa/errors.py @@ -351,6 +351,9 @@ class Error(Exception): def __init__(self, description): super(Error, self).__init__(description) + + def __eq__(self, other): + return self.__dict__ == other.__dict__ class VisaIOError(Error): @@ -368,6 +371,9 @@ class VisaIOError(Error): self.error_code = error_code self.abbreviation = abbreviation self.description = description + + def __reduce__(self): + return (VisaIOError, (self.error_code,)) class VisaIOWarning(Warning): @@ -384,6 +390,12 @@ class VisaIOWarning(Warning): self.error_code = error_code self.abbreviation = abbreviation self.description = description + + def __reduce__(self): + return (VisaIOWarning, (self.error_code,)) + + def __eq__(self, other): + return self.__dict__ == other.__dict__ class VisaTypeError(Error): @@ -410,12 +422,22 @@ class UnknownHandler(Error): def __init__(self, event_type, handler, user_handle): super(UnknownHandler, self).__init__('%s, %s, %s' % (event_type, handler, user_handle)) + self.event_type = event_type + self.handler = handler + self.user_handle = user_handle + + def __reduce__(self): + return (UnknownHandler, (self.event_type, self.handler, self.user_handle)) class OSNotSupported(Error): def __init__(self, os): super(OSNotSupported, self).__init__(os + " is not yet supported by PyVISA") + self.os = os + + def __reduce__(self): + return (OSNotSupported, (self.os,)) class InvalidBinaryFormat(Error): @@ -424,6 +446,10 @@ class InvalidBinaryFormat(Error): if description: description = ": " + description super(InvalidBinaryFormat, self).__init__("Unrecognized binary data format" + description) + self.description = description + + def __reduce__(self): + return (InvalidBinaryFormat, (self.description,)) class InvalidSession(Error): @@ -433,6 +459,9 @@ class InvalidSession(Error): def __init__(self): super(InvalidSession, self).__init__('Invalid session handle. The resource might be closed.') + def __reduce__(self): + return (InvalidSession, ()) + class LibraryError(OSError, Error): diff --git a/pyvisa/highlevel.py b/pyvisa/highlevel.py index a07f34a..b6f610b 100644 --- a/pyvisa/highlevel.py +++ b/pyvisa/highlevel.py @@ -16,6 +16,7 @@ from __future__ import division, unicode_literals, print_function, absolute_impo import contextlib import collections import pkgutil +import os from collections import defaultdict from . import logger @@ -246,7 +247,7 @@ class VisaLibraryBase(object): raise ValueError('%s is not a valid size. Valid values are 8, 16, 32 or 64' % width) def write_memory(self, session, space, offset, data, width, extended=False): - """Write in an 8-bit, 16-bit, 32-bit, value to the specified memory space and offset. + """Write in an 8-bit, 16-bit, 32-bit, 64-bit value to the specified memory space and offset. Corresponds to viOut* functions of the VISA library. @@ -265,8 +266,10 @@ class VisaLibraryBase(object): return self.out_16(session, space, offset, data, extended) elif width == 32: return self.out_32(session, space, offset, data, extended) + elif width == 64: + return self.out_64(session, space, offset, data, extended) - raise ValueError('%s is not a valid size. Valid values are 8, 16 or 32' % width) + raise ValueError('%s is not a valid size. Valid values are 8, 16, 32, or 64' % width) def move_in(self, session, space, offset, length, width, extended=False): """Moves a block of data to local memory from the specified address space and offset. @@ -322,7 +325,7 @@ class VisaLibraryBase(object): raise ValueError('%s is not a valid size. Valid values are 8, 16, 32 or 64' % width) def peek(self, session, address, width): - """Read an 8, 16 or 32-bit value from the specified address. + """Read an 8, 16, 32, or 64-bit value from the specified address. Corresponds to viPeek* functions of the VISA library. @@ -345,7 +348,7 @@ class VisaLibraryBase(object): raise ValueError('%s is not a valid size. Valid values are 8, 16, 32 or 64' % width) def poke(self, session, address, width, data): - """Writes an 8, 16 or 32-bit value from the specified address. + """Writes an 8, 16, 32, or 64-bit value from the specified address. Corresponds to viPoke* functions of the VISA library. @@ -363,8 +366,10 @@ class VisaLibraryBase(object): return self.poke_16(session, address, data) elif width == 32: return self.poke_32(session, address, data) + elif width == 64: + return self.poke_64(session, address, data) - raise ValueError('%s is not a valid size. Valid values are 8, 16 or 32' % width) + raise ValueError('%s is not a valid size. Valid values are 8, 16, 32, or 64' % width) # Methods that VISA Library implementations must implement @@ -1035,7 +1040,8 @@ class VisaLibraryBase(object): return (ResourceInfo(parsed.interface_type_const, parsed.board, - parsed.resource_class, None, None), + parsed.resource_class, + str(parsed), None), constants.StatusCode.success) except ValueError: return 0, constants.StatusCode.error_invalid_resource_name @@ -1411,7 +1417,7 @@ def list_backends(): :rtype: list """ return ['ni'] + [name for (loader, name, ispkg) in pkgutil.iter_modules() - if name.startswith('pyvisa-')] + if name.startswith('pyvisa-') and not name.endswith('-script')] #: Maps backend name to VisaLibraryBase derived class @@ -1432,12 +1438,11 @@ def get_wrapper_class(backend_name): _WRAPPERS['ni'] = NIVisaLibrary return NIVisaLibrary - for pkgname in list_backends(): - if pkgname.endswith('-' + backend_name): - pkg = __import__(pkgname) - _WRAPPERS[backend_name] = cls = pkg.WRAPPER_CLASS - return cls - else: + try: + pkg = __import__('pyvisa-' + backend_name) + _WRAPPERS[backend_name] = cls = pkg.WRAPPER_CLASS + return cls + except ImportError: raise ValueError('Wrapper not found: No package named pyvisa-%s' % backend_name) @@ -1448,6 +1453,12 @@ def open_visa_library(specification): wrapper will be created automatically when you create a ResourceManager object. """ + if not specification: + try: + specification = os.environ['PYVISA_LIBRARY'] + except KeyError: + logger.debug('No visa libaray specified and environment variable PYVISA_LIBRARY is unset. Using NI library') + try: argument, wrapper = specification.split('@') except ValueError: diff --git a/pyvisa/resources/messagebased.py b/pyvisa/resources/messagebased.py index 2bf22e6..588ed26 100644 --- a/pyvisa/resources/messagebased.py +++ b/pyvisa/resources/messagebased.py @@ -11,7 +11,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 contextlib import time @@ -47,7 +48,8 @@ class ValuesFormat(object): self.container = container self.is_binary = False - def use_binary(self, datatype, is_big_endian, container=list, header_fmt='ieee'): + def use_binary(self, datatype, is_big_endian, container=list, + header_fmt='ieee'): self.datatype = datatype self.is_big_endian = is_big_endian self.container = container @@ -56,19 +58,21 @@ class ValuesFormat(object): class ControlRenMixin(object): - """Common controlt_ren method of some messaged based resources. + """Common control_ren method of some messaged based resources. + """ # It should work for GPIB, USB and some TCPIP - # For TCPIP I found some (all?) NI's VISA library do not handle control_ren, but - # it works for Agilent's VISA library (at least some of them) + # For TCPIP I found some (all?) NI's VISA library do not handle + # control_ren, but it works for Agilent's VISA library (at least some of + # them) def control_ren(self, mode): - """Controls the state of the GPIB Remote Enable (REN) interface line, and optionally the remote/local - state of the device. + """Controls the state of the GPIB Remote Enable (REN) interface line, + and optionally the remote/local state of the device. Corresponds to viGpibControlREN function of the VISA library. - :param mode: Specifies the state of the REN line and optionally the device remote/local state. - (Constants.GPIB_REN*) + :param mode: Specifies the state of the REN line and optionally the + device remote/local state. (Constants.GPIB_REN*) :return: return value of the library call. :rtype: VISAStatus """ @@ -95,17 +99,21 @@ class MessageBasedResource(Resource): self._values_format = ValuesFormat() super(MessageBasedResource, self).__init__(*args, **kwargs) - # This is done for backwards compatibility but will be removed in 1.7 + # This is done for backwards compatibility but will be removed in 1.10 @property def values_format(self): + warnings.warn('values_format is deprecated and will be removed in ' + '1.10', FutureWarning) return self._values_format @values_format.setter def values_format(self, fmt): + warnings.warn('values_format is deprecated and will be removed in ' + '1.10', FutureWarning) self._values_format.is_binary = not (fmt & 0x01 == 0) - if fmt & 0x03 == 1: #single + if fmt & 0x03 == 1: # single self._values_format.datatype = 'f' - elif fmt & 0x03 == 3: #double: + elif fmt & 0x03 == 3: # double: self._values_format.datatype = 'd' else: raise ValueError("unknown data values fmt requested") @@ -118,10 +126,14 @@ class MessageBasedResource(Resource): def ask_delay(self): """An alias for query_delay kept for backwards compatibility. """ + warnings.warn('ask_delay is deprecated and will be removed in ' + '1.10, use query_delay instead', FutureWarning) return self.query_delay @ask_delay.setter def ask_delay(self, value): + warnings.warn('ask_delay is deprecated and will be removed in ' + '1.10, use query_delay instead', FutureWarning) self.query_delay = value @property @@ -132,7 +144,8 @@ class MessageBasedResource(Resource): @encoding.setter def encoding(self, encoding): - _ = 'test encoding'.encode(encoding).decode(encoding) + # Test that the encoding specified makes sense. + 'test encoding'.encode(encoding).decode(encoding) self._encoding = encoding @property @@ -145,22 +158,25 @@ class MessageBasedResource(Resource): def read_termination(self, value): if value: - # termination character, the rest is just used for verification after - # each read operation. + # termination character, the rest is just used for verification + # after each read operation. last_char = value[-1:] - # Consequently, it's illogical to have the real termination character - # twice in the sequence (otherwise reading would stop prematurely). + # Consequently, it's illogical to have the real termination + # character twice in the sequence (otherwise reading would stop + # prematurely). if last_char in value[:-1]: raise ValueError("ambiguous ending in termination characters") self.set_visa_attribute(constants.VI_ATTR_TERMCHAR, ord(last_char)) - self.set_visa_attribute(constants.VI_ATTR_TERMCHAR_EN, constants.VI_TRUE) + self.set_visa_attribute(constants.VI_ATTR_TERMCHAR_EN, + constants.VI_TRUE) else: - # The termchar is also used in VI_ATTR_ASRL_END_IN (for serial termination) - # so return it to its default. + # The termchar is also used in VI_ATTR_ASRL_END_IN (for serial + # termination) so return it to its default. self.set_visa_attribute(constants.VI_ATTR_TERMCHAR, ord(self.LF)) - self.set_visa_attribute(constants.VI_ATTR_TERMCHAR_EN, constants.VI_FALSE) + self.set_visa_attribute(constants.VI_ATTR_TERMCHAR_EN, + constants.VI_FALSE) self._read_termination = value @@ -208,8 +224,10 @@ class MessageBasedResource(Resource): return count - def write_ascii_values(self, message, values, converter='f', separator=',', termination=None, encoding=None): - """Write a string message to the device followed by values in ascii format. + def write_ascii_values(self, message, values, converter='f', separator=',', + termination=None, encoding=None): + """Write a string message to the device followed by values in ascii + format. The write_termination is always appended to it. @@ -220,8 +238,8 @@ class MessageBasedResource(Resource): String formatting codes are also accepted. Defaults to str. :type converter: callable | str - :param separator: a callable that split the str into individual elements. - If a str is given, data.split(separator) is used. + :param separator: a callable that join the values in a single str. + If a str is given, separator.join(values) is used. :type: separator: (collections.Iterable[T]) -> str | str :return: number of bytes written. :rtype: int @@ -236,7 +254,7 @@ class MessageBasedResource(Resource): block = util.to_ascii_block(values, converter, separator) - message = message.encode(enco) + block + message = message.encode(enco) + block.encode(enco) if term: message += term.encode(enco) @@ -245,15 +263,19 @@ class MessageBasedResource(Resource): return count - def write_binary_values(self, message, values, datatype='f', is_big_endian=False, termination=None, encoding=None): - """Write a string message to the device followed by values in binary format. + def write_binary_values(self, message, values, datatype='f', + is_big_endian=False, termination=None, + encoding=None): + """Write a string message to the device followed by values in binary + format. The write_termination is always appended to it. :param message: the message to be sent. :type message: unicode (Py2) or str (Py3) :param values: data to be writen to the device. - :param datatype: the format string for a single element. See struct module. + :param datatype: the format string for a single element. See struct + module. :param is_big_endian: boolean indicating endianess. :return: number of bytes written. :rtype: int @@ -277,36 +299,95 @@ class MessageBasedResource(Resource): return count def write_values(self, message, values, termination=None, encoding=None): - + warnings.warn('write_values is deprecated and will be removed in ' + '1.10, use write_ascii_values or write_binary_values ' + 'instead.', FutureWarning) vf = self.values_format if vf.is_binary: - return self.write_binary_values(message, values, vf.datatype, vf.is_big_endian, termination, encoding) + return self.write_binary_values(message, values, vf.datatype, + vf.is_big_endian, termination, + encoding) - return self.write_ascii_values(message, values, vf.converter, vf.separator, termination, encoding) + return self.write_ascii_values(message, values, vf.converter, + vf.separator, termination, encoding) + + def read_bytes(self, count, chunk_size=None, break_on_termchar=False): + """Read a certain number of bytes from the instrument. + + :param count: The number of bytes to read from the instrument. + :type count: int + :param chunk_size: The chunk size to use to perform the reading. + :type chunk_size: int + :param break_on_termchar: Should the reading stop when a termination + character is encountered. + :type brak_on_termchar: bool + + :rtype: bytes + + """ + chunk_size = chunk_size or self.chunk_size + ret = bytearray() + left_to_read = count + termchar_read = constants.StatusCode.success_termination_character_read + + with self.ignore_warning(constants.VI_SUCCESS_DEV_NPRESENT, + constants.VI_SUCCESS_MAX_CNT): + try: + status = None + while len(ret) < count: + size = min(chunk_size, left_to_read) + logger.debug('%s - reading %d bytes (last status %r)', + self._resource_name, size, status) + chunk, status = self.visalib.read(self.session, size) + ret.extend(chunk) + left_to_read -= len(chunk) + if break_on_termchar and status == termchar_read: + break + except errors.VisaIOError as e: + logger.debug('%s - exception while reading: %s\n' + 'Buffer content: %r', self._resource_name, e, + ret) + raise + return bytes(ret) def read_raw(self, size=None): """Read the unmodified string sent from the instrument to the computer. In contrast to read(), no termination characters are stripped. + :param size: The chunk size to use when reading the data. + :rtype: bytes """ + return bytes(self._read_raw(size)) + + def _read_raw(self, size=None): + """Read the unmodified string sent from the instrument to the computer. + + In contrast to read(), no termination characters are stripped. + + :param size: The chunk size to use when reading the data. + + :rtype: bytearray + """ size = self.chunk_size if size is None else size loop_status = constants.StatusCode.success_max_count_read - ret = bytes() - with self.ignore_warning(constants.VI_SUCCESS_DEV_NPRESENT, constants.VI_SUCCESS_MAX_CNT): + ret = bytearray() + with self.ignore_warning(constants.VI_SUCCESS_DEV_NPRESENT, + constants.VI_SUCCESS_MAX_CNT): try: status = loop_status while status == loop_status: logger.debug('%s - reading %d bytes (last status %r)', self._resource_name, size, status) chunk, status = self.visalib.read(self.session, size) - ret += chunk + ret.extend(chunk) except errors.VisaIOError as e: - logger.debug('%s - exception while reading: %s', self._resource_name, e) + logger.debug('%s - exception while reading: %s\nBuffer ' + 'content: %r', self._resource_name, e, ret) raise return ret @@ -329,10 +410,10 @@ class MessageBasedResource(Resource): if termination is None: termination = self._read_termination - message = self.read_raw().decode(enco) + message = self._read_raw().decode(enco) else: with self.read_termination_context(termination): - message = self.read_raw().decode(enco) + message = self._read_raw().decode(enco) if not termination: return message @@ -343,6 +424,78 @@ class MessageBasedResource(Resource): return message[:-len(termination)] + def read_ascii_values(self, converter='f', separator=',', container=list, + delay=None): + """Read values from the device in ascii format returning an iterable of + values. + + :param delay: delay in seconds between write and read operations. + if None, defaults to self.query_delay + :param converter: function used to convert each element. + Defaults to float + :type converter: callable + :param separator: a callable that split the str into individual + elements. If a str is given, data.split(separator) is + used. + :type: separator: (str) -> collections.Iterable[int] | str + :param container: container type to use for the output data. + :returns: the answer from the device. + :rtype: list + + """ + # Use read rather than _read_raw because we cannot handle a bytearray + block = self.read() + + return util.from_ascii_block(block, converter, separator, container) + + def read_binary_values(self, datatype='f', is_big_endian=False, + container=list, header_fmt='ieee'): + """Read values from the device in binary format returning an iterable + of values. + + :param datatype: the format string for a single element. See struct + module. + :param is_big_endian: boolean indicating endianess. + Defaults to False. + :param container: container type to use for the output data. + :param header_fmt: format of the header prefixing the data. Possible + values are: 'ieee', 'hp', 'empty' + :returns: the answer from the device. + :rtype: type(container) + + """ + block = self._read_raw() + expected_length = 0 + + if header_fmt == 'ieee': + offset, data_length = util.parse_ieee_block_header(block) + expected_length = offset + data_length + elif header_fmt == 'hp': + offset, data_length = util.parse_hp_block_header(block, + is_big_endian) + expected_length = offset + data_length + + if self._read_termination is not None: + expected_length += len(self._read_termination) + + while len(block) < expected_length: + block.extend(self._read_raw()) + + try: + if header_fmt == 'ieee': + return util.from_ieee_block(block, datatype, is_big_endian, + container) + elif header_fmt == 'hp': + return util.from_hp_block(block, datatype, is_big_endian, + container) + elif header_fmt == 'empty': + return util.from_binary_block(block, 0, None, datatype, + is_big_endian, container) + else: + raise + except ValueError as e: + raise errors.InvalidBinaryFormat(e.args) + def read_values(self, fmt=None, container=list): """Read a list of floating point values from the device. @@ -355,13 +508,17 @@ class MessageBasedResource(Resource): :return: the list of read values :rtype: list """ + warnings.warn('read_values is deprecated and will be removed in ' + '1.10, use read_ascii_values or read_binary_values ' + 'instead.', FutureWarning) if not fmt: vf = self.values_format if not vf.is_binary: return util.from_ascii_block(self.read(), container) - data = self.read_raw() + data = self._read_raw() try: - return util.parse_binary(data, vf.is_big_endian, vf.datatype=='f') + return util.parse_binary(data, vf.is_big_endian, + vf.datatype == 'f') except ValueError as e: try: msg = e.args[0] @@ -369,20 +526,20 @@ class MessageBasedResource(Resource): msg = '' raise errors.InvalidBinaryFormat(msg) - if fmt & 0x01 == 0: # ascii + if fmt & 0x01 == 0: # ascii return util.from_ascii_block(self.read()) - data = self.read_raw() + data = self._read_raw() try: - if fmt & 0x03 == 1: #single + if fmt & 0x03 == 1: # single is_single = True - elif fmt & 0x03 == 3: #double: + elif fmt & 0x03 == 3: # double: is_single = False else: raise ValueError("unknown data values fmt requested") - is_big_endian = fmt & 0x04 # big endian + is_big_endian = fmt & 0x04 # big endian return util.parse_binary(data, is_big_endian, is_single) except ValueError as e: raise errors.InvalidBinaryFormat(e.args) @@ -406,8 +563,10 @@ class MessageBasedResource(Resource): time.sleep(delay) return self.read() - # Kept for backwards compatibility. - ask = query + def ask(self, message, delay=None): + warnings.warn('ask is deprecated and will be removed in ' + '1.10, use query instead.', FutureWarning) + return self.query(message, delay) def query_values(self, message, delay=None): """Query the device for values returning an iterable of values. @@ -421,15 +580,23 @@ class MessageBasedResource(Resource): :returns: the answer from the device. :rtype: list """ + warnings.warn('query_values is deprecated and will be removed in ' + '1.10, use query_ascii_values or quey_binary_values ' + 'instead.', FutureWarning) vf = self.values_format if vf.is_binary: - return self.query_binary_values(message, vf.datatype, vf.is_big_endian, vf.container, delay, vf.header_fmt) + return self.query_binary_values(message, vf.datatype, + vf.is_big_endian, vf.container, + delay, vf.header_fmt) - return self.query_ascii_values(message, vf.converter, vf.separator, vf.container, delay) + return self.query_ascii_values(message, vf.converter, vf.separator, + vf.container, delay) - def query_ascii_values(self, message, converter='f', separator=',', container=list, delay=None): - """Query the device for values in ascii format returning an iterable of values. + def query_ascii_values(self, message, converter='f', separator=',', + container=list, delay=None): + """Query the device for values in ascii format returning an iterable of + values. :param message: the message to send. :type message: str @@ -438,8 +605,9 @@ class MessageBasedResource(Resource): :param converter: function used to convert each element. Defaults to float :type converter: callable - :param separator: a callable that split the str into individual elements. - If a str is given, data.split(separator) is used. + :param separator: a callable that split the str into individual + elements. If a str is given, data.split(separator) is + used. :type: separator: (str) -> collections.Iterable[int] | str :param container: container type to use for the output data. :returns: the answer from the device. @@ -452,25 +620,28 @@ class MessageBasedResource(Resource): if delay > 0.0: time.sleep(delay) - block = self.read() - - return util.from_ascii_block(block, converter, separator, container) + return self.read_ascii_values(converter, separator, container, + delay) - def query_binary_values(self, message, datatype='f', is_big_endian=False, container=list, delay=None, header_fmt='ieee'): - """Converts an iterable of numbers into a block of data in the ieee format. + def query_binary_values(self, message, datatype='f', is_big_endian=False, + container=list, delay=None, header_fmt='ieee'): + """Query the device for values in binary format returning an iterable + of values. :param message: the message to send to the instrument. - :param datatype: the format string for a single element. See struct module. + :param datatype: the format string for a single element. See struct + module. :param is_big_endian: boolean indicating endianess. Defaults to False. :param container: container type to use for the output data. :param delay: delay in seconds between write and read operations. if None, defaults to self.query_delay - :rtype: bytes + :returns: the answer from the device. + :rtype: list """ - if header_fmt not in ('ieee', 'empty', 'hp'): - raise ValueError("Invalid header format. Valid options are 'ieee', 'empty', 'hp'") + raise ValueError("Invalid header format. Valid options are 'ieee'," + " 'empty', 'hp'") self.write(message) if delay is None: @@ -478,24 +649,8 @@ class MessageBasedResource(Resource): if delay > 0.0: time.sleep(delay) - block = self.read_raw() - - if header_fmt == 'ieee': - offset, data_length = util.parse_ieee_block_header(block) - expected_length = offset + data_length - - while len(block) < expected_length: - block += self.read_raw() - - try: - if header_fmt == 'ieee': - return util.from_ieee_block(block, datatype, is_big_endian, container) - elif header_fmt == 'empty': - return util.from_binary_block(block, 0, None, datatype, is_big_endian, container) - elif header_fmt == 'hp': - return util.from_binary_block(block, 4, None, datatype, is_big_endian, container) - except ValueError as e: - raise errors.InvalidBinaryFormat(e.args) + return self.read_binary_values(datatype, is_big_endian, container, + header_fmt) def ask_for_values(self, message, fmt=None, delay=None): """A combination of write(message) and read_values() @@ -507,7 +662,9 @@ class MessageBasedResource(Resource): :returns: the answer from the device. :rtype: list """ - + warnings.warn('ask_values is deprecated and will be removed in ' + '1.10, use query_ascii_values or quey_binary_values ' + 'instead.', FutureWarning) self.write(message) if delay is None: delay = self.query_delay @@ -519,7 +676,8 @@ class MessageBasedResource(Resource): """Sends a software trigger to the device. """ - self.visalib.assert_trigger(self.session, constants.VI_TRIG_PROT_DEFAULT) + self.visalib.assert_trigger(self.session, + constants.VI_TRIG_PROT_DEFAULT) @property def stb(self): @@ -536,10 +694,12 @@ class MessageBasedResource(Resource): @contextlib.contextmanager def read_termination_context(self, new_termination): term = self.get_visa_attribute(constants.VI_ATTR_TERMCHAR) - self.set_visa_attribute(constants.VI_ATTR_TERMCHAR, ord(new_termination[-1])) + self.set_visa_attribute(constants.VI_ATTR_TERMCHAR, + ord(new_termination[-1])) yield self.set_visa_attribute(constants.VI_ATTR_TERMCHAR, term) # Rohde and Schwarz Device via Passport. Not sure which Resource should be. -MessageBasedResource.register(constants.InterfaceType.rsnrp, 'INSTR')(MessageBasedResource) +MessageBasedResource.register(constants.InterfaceType.rsnrp, + 'INSTR')(MessageBasedResource) diff --git a/pyvisa/resources/resource.py b/pyvisa/resources/resource.py index 4d33e91..46c4963 100644 --- a/pyvisa/resources/resource.py +++ b/pyvisa/resources/resource.py @@ -113,6 +113,12 @@ class Resource(object): def __repr__(self): return "<%r(%r)>" % (self.__class__.__name__, self.resource_name) + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + @property def last_status(self): """Last status code for this session. @@ -261,8 +267,10 @@ class Resource(object): :param name: Attribute for which the state is to be modified. (Attributes.*) :param state: The state of the attribute to be set for the specified object. + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` """ - self.visalib.set_attribute(self.session, name, state) + return self.visalib.set_attribute(self.session, name, state) def clear(self): """Clears this resource @@ -334,7 +342,7 @@ class Resource(object): event_type, context, ret = self.visalib.wait_on_event(self.session, in_event_type, timeout) except errors.VisaIOError as exc: if capture_timeout and exc.error_code == constants.StatusCode.error_timeout: - return WaitResponse(0, None, exc.error_code, self.visalib, timed_out=True) + return WaitResponse(in_event_type, None, exc.error_code, self.visalib, timed_out=True) raise return WaitResponse(event_type, context, ret, self.visalib) diff --git a/pyvisa/shell.py b/pyvisa/shell.py index ef2175d..755b49b 100644 --- a/pyvisa/shell.py +++ b/pyvisa/shell.py @@ -18,7 +18,7 @@ import cmd import sys from .compat import input -from . import ResourceManager, constants, VisaIOError +from . import ResourceManager, attributes, constants, VisaIOError from .thirdparty import prettytable if sys.platform == 'darwin': @@ -56,7 +56,7 @@ if sys.platform == 'darwin': if intro is not None: self.intro = intro if self.intro: - self.stdout.write(str(self.intro)+"\n") + self.stdout.write(str(self.intro) + "\n") stop = None while not stop: if self.cmdqueue: @@ -129,7 +129,7 @@ class VisaShell(Cmd): self.resources = [] for ndx, (resource_name, value) in enumerate(resources.items()): if not args: - print('({:2d}) {}'.format(ndx, resource_name)) + print('({0:2d}) {1}'.format(ndx, resource_name)) if value.alias: print(' alias: {}'.format(value.alias)) @@ -156,8 +156,8 @@ class VisaShell(Cmd): try: self.current = self.resource_manager.open_resource(args) print('{} has been opened.\n' - 'You can talk to the device using "write", "read" or "query.\n' - 'The default end of message is added to each message'.format(args)) + 'You can talk to the device using "write", "read" or "query".\n' + 'The default end of message is added to each message.'.format(args)) self.py_attr = [] self.vi_attr = [] @@ -228,6 +228,38 @@ class VisaShell(Cmd): except Exception as e: print(e) + def do_timeout(self, args): + """Get or set timeout (in ms) for resource in use. + + Get timeout: + + timeout + + Set timeout: + + timeout <mstimeout> + + """ + + if not self.current: + print('There are no resources in use. Use the command "open".') + return + + args = args.strip() + + if not args: + try: + print('Timeout: {}ms'.format(self.current.timeout)) + except Exception as e: + print(e) + else: + args = args.split(' ') + try: + self.current.timeout = float(args[0]) + print('Done') + except Exception as e: + print(e) + def print_attribute_list(self): p = prettytable.PrettyTable(('VISA name', 'Constant', 'Python name', 'val')) for attr in getattr(self.current, 'visa_attributes_classes', ()): @@ -290,8 +322,28 @@ class VisaShell(Cmd): attr_name, attr_state = args[0], args[1] if attr_name.startswith('VI_'): try: - self.current.set_visa_attribute(getattr(constants, attr_name), attr_state) - print('Done') + attributeId = getattr(constants, attr_name) + attr = attributes.AttributesByID[attributeId] + datatype = attr.visa_type + retcode = None + if datatype == 'ViBoolean': + if attr_state == 'True': + attr_state = True + elif attr_state == 'False': + attr_state = False + else: + retcode = constants.StatusCode.error_nonsupported_attribute_state + elif datatype in ['ViUInt8', 'ViUInt16', 'ViUInt32', 'ViInt8', 'ViInt16', 'ViInt32']: + try: + attr_state = int(attr_state) + except ValueError: + retcode = constants.StatusCode.error_nonsupported_attribute_state + if not retcode: + retcode = self.current.set_visa_attribute(attributeId, attr_state) + if retcode: + print('Error {}'.format(str(retcode))) + else: + print('Done') except Exception as e: print(e) else: @@ -307,6 +359,55 @@ class VisaShell(Cmd): return [item for item in self.py_attr if item.startswith(text)] + \ [item for item in self.vi_attr if item.startswith(text)] + def do_termchar(self, args): + """Get or set termination character for resource in use. + <termchar> can be one of: CR, LF, CRLF, NUL or None. + None is used to disable termination character + + Get termination character: + + termchar + + Set termination character read or read+write: + + termchar <termchar> [<termchar>] + + """ + + if not self.current: + print('There are no resources in use. Use the command "open".') + return + + args = args.strip() + + if not args: + try: + charmap = { u'\r': 'CR', u'\n': 'LF', u'\r\n': 'CRLF', u'\0': 'NUL' } + chr = self.current.read_termination + if chr in charmap: + chr = charmap[chr] + chw = self.current.write_termination + if chw in charmap: + chw = charmap[chw] + print('Termchar read: {} write: {}'.format(chr, chw)) + except Exception as e: + print(e) + else: + args = args.split(' ') + charmap = { 'CR': u'\r', 'LF': u'\n', 'CRLF': u'\r\n', 'NUL': u'\0', 'None': None } + chr = args[0] + chw = args[0 if len(args) == 1 else 1] + if chr in charmap and chw in charmap: + try: + self.current.read_termination = charmap[chr] + self.current.write_termination = charmap[chw] + print('Done') + except Exception as e: + print(e) + else: + print('use CR, LF, CRLF, NUL or None to set termchar') + return + def do_exit(self, arg): """Exit the shell session.""" diff --git a/pyvisa/testsuite/__init__.py b/pyvisa/testsuite/__init__.py index e46a262..acd8991 100644 --- a/pyvisa/testsuite/__init__.py +++ b/pyvisa/testsuite/__init__.py @@ -1,15 +1,18 @@ # -*- 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 import logging +import warnings +import unittest from contextlib import contextmanager from logging.handlers import BufferingHandler from pyvisa import logger -from pyvisa.compat import unittest +from pyvisa.compat import PYTHON3 class TestHandler(BufferingHandler): @@ -62,6 +65,21 @@ class BaseTestCase(unittest.TestCase): msg = '\n'.join(record.get('msg', str(record)) for record in buf) self.assertEqual(l, 0, msg='%d warnings raised.\n%s' % (l, msg)) + if not PYTHON3: + @contextmanager + def assertWarns(self, category): + """Backport for Python 2 + + """ + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + # Trigger a warning. + yield + # Verify some things + assert len(w) == 1, 'No warning raised' + assert issubclass(w[-1].category, category) + def testsuite(): """A testsuite that has all the pyvisa tests. @@ -85,4 +103,3 @@ def run(): """ test_runner = unittest.TextTestRunner() return test_runner.run(testsuite()) - diff --git a/pyvisa/testsuite/test_errors.py b/pyvisa/testsuite/test_errors.py new file mode 100644 index 0000000..bcecdec --- /dev/null +++ b/pyvisa/testsuite/test_errors.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*-
+
+from __future__ import division, unicode_literals, print_function, absolute_import
+
+import pickle
+
+from pyvisa.testsuite import BaseTestCase
+
+from pyvisa import errors
+
+
+class TestPicleUnpickle(BaseTestCase):
+ def _test_pickle_unpickle(self, instance):
+ pickled = pickle.dumps(instance)
+ unpickled = pickle.loads(pickled)
+ self.assertIsInstance(unpickled, type(instance))
+ self.assertEqual(instance, unpickled)
+
+ def test_VisaIOError(self):
+ self._test_pickle_unpickle(errors.VisaIOError(0))
+
+ def test_VisaIOWarning(self):
+ self._test_pickle_unpickle(errors.VisaIOWarning(0))
+
+ def test_UnknownHandler(self):
+ self._test_pickle_unpickle(errors.UnknownHandler(0,0,0))
+
+ def test_OSNotSupported(self):
+ self._test_pickle_unpickle(errors.OSNotSupported(""))
+
+ def test_InvalidBinaryFormat(self):
+ self._test_pickle_unpickle(errors.InvalidBinaryFormat())
+
+ def InvalidSession(self):
+ self._test_pickle_unpickle(errors.InvalidSession())
diff --git a/pyvisa/testsuite/test_rname.py b/pyvisa/testsuite/test_rname.py index f6c694b..55193ba 100644 --- a/pyvisa/testsuite/test_rname.py +++ b/pyvisa/testsuite/test_rname.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- -from __future__ import division, unicode_literals, print_function, absolute_import +from __future__ import (division, unicode_literals, print_function, + absolute_import) -from pyvisa.compat import unittest +import unittest from pyvisa.testsuite import BaseTestCase from pyvisa import rname diff --git a/pyvisa/testsuite/test_util.py b/pyvisa/testsuite/test_util.py index 85ff8c7..00501eb 100644 --- a/pyvisa/testsuite/test_util.py +++ b/pyvisa/testsuite/test_util.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -from __future__ import division, unicode_literals, print_function, absolute_import +from __future__ import (division, unicode_literals, print_function, + absolute_import) from pyvisa.testsuite import BaseTestCase @@ -12,49 +13,102 @@ try: except ImportError: np = None + class TestParser(BaseTestCase): def test_parse_binary(self): - s = b'#A@\xe2\x8b<@\xe2\x8b<@\xe2\x8b<@\xe2\x8b<@\xde\x8b<@\xde\x8b<@\xde\x8b<' \ - b'@\xde\x8b<@\xe0\x8b<@\xe0\x8b<@\xdc\x8b<@\xde\x8b<@\xe2\x8b<@\xe0\x8b<' + s = (b'#A@\xe2\x8b<@\xe2\x8b<@\xe2\x8b<@\xe2\x8b<@\xde\x8b<@\xde\x8b<@' + b'\xde\x8b<@\xde\x8b<@\xe0\x8b<@\xe0\x8b<@\xdc\x8b<@\xde\x8b<@' + b'\xe2\x8b<@\xe0\x8b<') e = [0.01707566, 0.01707566, 0.01707566, 0.01707566, 0.01707375, 0.01707375, 0.01707375, 0.01707375, 0.01707470, 0.01707470, 0.01707280, 0.01707375, 0.01707566, 0.01707470] - p = util.parse_binary(s, is_big_endian=False, is_single=True) + with self.assertWarns(FutureWarning): + p = util.parse_binary(s, is_big_endian=False, is_single=True) for a, b in zip(p, e): self.assertAlmostEqual(a, b) + + # Test handling indefinite length block p = util.from_ieee_block(s, datatype='f', is_big_endian=False) for a, b in zip(p, e): self.assertAlmostEqual(a, b) - def test_ieee_integer(self): + # Test handling definite length block + p = util.from_ieee_block(b'#214' + s[2:], datatype='f', + is_big_endian=False) + for a, b in zip(p, e): + self.assertAlmostEqual(a, b) + + p = util.from_hp_block(b'#A\x0e\x00' + s[2:], datatype='f', + is_big_endian=False) + for a, b in zip(p, e): + self.assertAlmostEqual(a, b) + + def test_integer_ascii_block(self): values = list(range(99)) - containers = (list, tuple) #+ ((np.asarray,) if np else ()) - for fmt in 'bBhHiIfd': - for endi in (True, False): - for cont in containers: - conv = cont(values) - msg = 'fmt=%s, endianness=%s, container=%s' % (fmt, endi, cont.__name__) - try: - block = util.to_ieee_block(conv, fmt, endi) - parsed = util.from_ieee_block(block, fmt, endi, cont) - except Exception as e: - raise Exception(msg + '\n' + repr(e)) - - self.assertEqual(conv, parsed, msg) - - def test_ieee_noninteger(self): + for fmt in 'd': + msg = 'block=%s, fmt=%s' + msg = msg % ('ascii', fmt) + tb = lambda values: util.to_ascii_block(values, fmt, ',') + fb = lambda block, cont: util.from_ascii_block(block, fmt, ',', + cont) + self.round_trip_block_converstion(values, tb, fb, msg) + + def test_non_integer_ascii_block(self): values = [val + 0.5 for val in range(99)] - containers = (list, tuple) #+ ((np.asarray,) if np else ()) - for fmt in 'fd': - for endi in (True, False): - for cont in containers: - conv = cont(values) - msg = 'fmt=%s, endianness=%s, container=%s' % (fmt, endi, cont.__name__) - try: - block = util.to_ieee_block(conv, fmt, endi) - parsed = util.from_ieee_block(block, fmt, endi, cont) - except Exception as e: - raise Exception(msg + '\n' + repr(e)) - - self.assertEqual(conv, parsed, msg) + values = list(range(99)) + for fmt in 'fFeEgG': + msg = 'block=%s, fmt=%s' + msg = msg % ('ascii', fmt) + tb = lambda values: util.to_ascii_block(values, fmt, ',') + fb = lambda block, cont: util.from_ascii_block(block, fmt, ',', + cont) + self.round_trip_block_converstion(values, tb, fb, msg) + + def test_integer_binary_block(self): + values = list(range(99)) + for block, tb, fb in zip(('ieee', 'hp'), + (util.to_ieee_block, util.to_hp_block), + (util.from_ieee_block, util.from_hp_block)): + for fmt in 'bBhHiIfd': + for endi in (True, False): + msg = 'block=%s, fmt=%s, endianness=%s' + msg = msg % (block, fmt, endi) + tblock = lambda values: tb(values, fmt, endi) + fblock = lambda block, cont: fb(block, fmt, endi, cont) + self.round_trip_block_converstion(values, tblock, fblock, + msg) + + def test_noninteger_binary_block(self): + values = [val + 0.5 for val in range(99)] + for block, tb, fb in zip(('ieee', 'hp'), + (util.to_ieee_block, util.to_hp_block), + (util.from_ieee_block, util.from_hp_block)): + for fmt in 'fd': + for endi in (True, False): + msg = 'block=%s, fmt=%s, endianness=%s' + msg = msg % (block, fmt, endi) + tblock = lambda values: bytearray(tb(values, fmt, endi)) + fblock = lambda block, cont: fb(block, fmt, endi, cont) + self.round_trip_block_converstion(values, tblock, fblock, + msg) + + def round_trip_block_converstion(self, values, to_block, from_block, msg): + """Test that block conversion round trip as expected. + + """ + containers = (list, tuple) + ((np.array,) if np else ()) + for cont in containers: + conv = cont(values) + msg += ', container=%s' + msg = msg % cont.__name__ + try: + block = to_block(conv) + parsed = from_block(block, cont) + except Exception as e: + raise Exception(msg + '\n' + repr(e)) + + if np and cont in (np.array,): + np.testing.assert_array_equal(conv, parsed, msg) + else: + self.assertEqual(conv, parsed, msg) diff --git a/pyvisa/util.py b/pyvisa/util.py index 8fcbf1f..7b01435 100644 --- a/pyvisa/util.py +++ b/pyvisa/util.py @@ -11,7 +11,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 functools import io @@ -20,11 +21,34 @@ import platform import sys import subprocess import warnings +import inspect +from subprocess import check_output -from .compat import check_output, string_types, OrderedDict, struct +from .compat import (string_types, OrderedDict, struct, + int_to_bytes, int_from_bytes, PYTHON3) from . import __version__, logger +try: + import numpy as np +except ImportError: + np = None + + +def _use_numpy_routines(container): + """Should optimized numpy routines be used to extract the data. + + """ + if np is None or container in (tuple, list): + return False + + if (container is np.array or (inspect.isclass(container) and + issubclass(container, np.ndarray))): + return True + + return False + + def read_user_library_path(): """Return the library path stored in one of the following configuration files: @@ -43,13 +67,16 @@ def read_user_library_path(): """ try: - from ConfigParser import SafeConfigParser as ConfigParser, NoSectionError + from ConfigParser import (SafeConfigParser as ConfigParser, + NoSectionError) except ImportError: from configparser import ConfigParser, NoSectionError config_parser = ConfigParser() - files = config_parser.read([os.path.join(sys.prefix, "share", "pyvisa", ".pyvisarc"), - os.path.join(os.path.expanduser("~"), ".pyvisarc")]) + files = config_parser.read([os.path.join(sys.prefix, "share", "pyvisa", + ".pyvisarc"), + os.path.join(os.path.expanduser("~"), + ".pyvisarc")]) if not files: logger.debug('No user defined library files') @@ -151,6 +178,16 @@ _converters = { 'G': float, } +_np_converters = { + 'd': 'i', + 'e': 'f', + 'E': 'f', + 'f': 'f', + 'F': 'f', + 'g': 'f', + 'G': 'f', +} + def from_ascii_block(ascii_data, converter='f', separator=',', container=list): """Parse ascii data and return an iterable of numbers. @@ -165,12 +202,19 @@ def from_ascii_block(ascii_data, converter='f', separator=',', container=list): :type: separator: (str) -> collections.Iterable[T] | str :param container: container type to use for the output data. """ + if (_use_numpy_routines(container) and + isinstance(converter, string_types) and + isinstance(separator, string_types) and + converter in _np_converters): + return np.fromstring(ascii_data, _np_converters[converter], + sep=separator) if isinstance(converter, string_types): try: converter = _converters[converter] except KeyError: - raise ValueError('Invalid code for converter: %s not in %s' % (converter, str(tuple(_converters.keys())))) + raise ValueError('Invalid code for converter: %s not in %s' % + (converter, str(tuple(_converters.keys())))) if isinstance(separator, string_types): data = ascii_data.split(separator) @@ -181,7 +225,7 @@ def from_ascii_block(ascii_data, converter='f', separator=',', container=list): def to_ascii_block(iterable, converter='f', separator=','): - """Parse ascii data and return an iterable of numbers. + """Turn an iterable of numbers in an ascii block of data. :param iterable: data to be parsed. :type iterable: collections.Iterable[T] @@ -192,6 +236,8 @@ def to_ascii_block(iterable, converter='f', separator=','): :param separator: a callable that split the str into individual elements. If a str is given, data.split(separator) is used. :type: separator: (collections.Iterable[T]) -> str | str + + :rtype: str """ if isinstance(separator, string_types): @@ -199,9 +245,10 @@ def to_ascii_block(iterable, converter='f', separator=','): if isinstance(converter, string_types): converter = '%' + converter - return separator(converter % val for val in iterable) + block = separator(converter % val for val in iterable) else: - return separator(converter(val) for val in iterable) + block = separator(converter(val) for val in iterable) + return block def parse_binary(bytes_data, is_big_endian=False, is_single=False): @@ -214,6 +261,9 @@ def parse_binary(bytes_data, is_big_endian=False, is_single=False): :param is_single: boolean indicating the type (if not is double) :return: """ + warnings.warn('parse_binary is deprecated and will be removed in ' + '1.10, use read_ascii_values or read_binary_values ' + 'instead.', FutureWarning) data = bytes_data hash_sign_position = bytes_data.find(b"#") @@ -269,20 +319,20 @@ def parse_ieee_block_header(block): #0<data> :param block: IEEE block. - :type block: bytes + :type block: bytes | bytearray :return: (offset, data_length) :rtype: (int, int) """ begin = block.find(b'#') if begin < 0: - raise ValueError("Could not find hash sign (#) indicating the start of the block.") + raise ValueError("Could not find hash sign (#) indicating the start of" + " the block.") try: # int(block[begin+1]) != int(block[begin+1:begin+2]) in Python 3 header_length = int(block[begin+1:begin+2]) except ValueError: header_length = 0 - offset = begin + 2 + header_length if header_length > 0: @@ -297,6 +347,35 @@ def parse_ieee_block_header(block): return offset, data_length +def parse_hp_block_header(block, is_big_endian): + """Parse the header of a HP block. + + Definite Length Arbitrary Block: + #A<data_length><data> + + The header ia always 4 bytes long. + The data_length field specifies the size of the data. + + :param block: HP block. + :type block: bytes | bytearray + :param is_big_endian: boolean indicating endianess. + :return: (offset, data_length) + :rtype: (int, int) + + """ + begin = block.find(b'#A') + if begin < 0: + raise ValueError("Could not find the standard block header (#A) " + "indicating the start of the block.") + offset = begin + 4 + + data_length = int_from_bytes(block[begin+2:offset], + byteorder='big' if is_big_endian else 'little' + ) + + return offset, data_length + + def from_ieee_block(block, datatype='f', is_big_endian=False, container=list): """Convert a block in the IEEE format into an iterable of numbers. @@ -309,38 +388,68 @@ def from_ieee_block(block, datatype='f', is_big_endian=False, container=list): Indefinite Length Arbitrary Block: #0<data> - :param block: IEEE block. - :type block: bytes + :param block: HP block. + :type block: bytes | bytearray :param datatype: the format string for a single element. See struct module. :param is_big_endian: boolean indicating endianess. :param container: container type to use for the output data. :return: items :rtype: type(container) """ - offset, data_length = parse_ieee_block_header(block) if len(block) < offset + data_length: - raise ValueError("Binary data is incomplete. The header states %d data bytes, " - "but %d where received." % (data_length, len(block) - offset)) + raise ValueError("Binary data is incomplete. The header states %d data" + " bytes, but %d where received." % + (data_length, len(block) - offset)) + + return from_binary_block(block, offset, data_length, datatype, + is_big_endian, container) - return from_binary_block(block, offset, data_length, datatype, is_big_endian, container) +def from_hp_block(block, datatype='f', is_big_endian=False, container=list): + """Convert a block in the HP format into an iterable of numbers. -def from_binary_block(block, offset=0, data_length=None, datatype='f', is_big_endian=False, container=list): + Definite Length Arbitrary Block: + #A<data_length><data> + + The header ia always 4 bytes long. + The data_length field specifies the size of the data. + + :param block: IEEE block. + :type block: bytes | bytearray + :param datatype: the format string for a single element. See struct module. + :param is_big_endian: boolean indicating endianess. + :param container: container type to use for the output data. + :return: items + :rtype: type(container) + """ + offset, data_length = parse_hp_block_header(block, is_big_endian) + + if len(block) < offset + data_length: + raise ValueError("Binary data is incomplete. The header states %d data" + " bytes, but %d where received." % + (data_length, len(block) - offset)) + + return from_binary_block(block, offset, data_length, datatype, + is_big_endian, container) + + +def from_binary_block(block, offset=0, data_length=None, datatype='f', + is_big_endian=False, container=list): """Convert a binary block into an iterable of numbers. :param block: binary block. - :type block: bytes + :type block: bytes | bytearray :param offset: offset at which the data block starts (default=0) - :param data_length: size in bytes of the data block (default=len(block) - offset) + :param data_length: size in bytes of the data block + (default=len(block) - offset) :param datatype: the format string for a single element. See struct module. :param is_big_endian: boolean indicating endianess. :param container: container type to use for the output data. :return: items :rtype: type(container) """ - if data_length is None: data_length = len(block) - offset @@ -349,6 +458,9 @@ def from_binary_block(block, offset=0, data_length=None, datatype='f', is_big_en endianess = '>' if is_big_endian else '<' + if _use_numpy_routines(container): + return np.frombuffer(block, endianess+datatype, array_length, offset) + fullfmt = '%s%d%s' % (endianess, array_length, datatype) try: @@ -357,6 +469,27 @@ def from_binary_block(block, offset=0, data_length=None, datatype='f', is_big_en raise ValueError("Binary data was malformed") +def to_binary_block(iterable, header, datatype, is_big_endian): + """Convert an iterable of numbers into a block of data with a given header. + + :param iterable: an iterable of numbers. + :param header: the header which should prefix the binary block + :param datatype: the format string for a single element. See struct module. + :param is_big_endian: boolean indicating endianess. + :return: IEEE block. + :rtype: bytes + """ + array_length = len(iterable) + + endianess = '>' if is_big_endian else '<' + fullfmt = '%s%d%s' % (endianess, array_length, datatype) + + if isinstance(header, string_types): + header = bytes(header, 'ascii') if PYTHON3 else str(header) + + return header + struct.pack(fullfmt, *iterable) + + def to_ieee_block(iterable, datatype='f', is_big_endian=False): """Convert an iterable of numbers into a block of data in the IEEE format. @@ -366,21 +499,34 @@ def to_ieee_block(iterable, datatype='f', is_big_endian=False): :return: IEEE block. :rtype: bytes """ - array_length = len(iterable) element_length = struct.calcsize(datatype) data_length = array_length * element_length header = '%d' % data_length - header = '#%d%s'%(len(header),header) + header = '#%d%s' % (len(header), header) - endianess = '>' if is_big_endian else '<' - fullfmt = '%s%d%s' % (endianess, array_length, datatype) + return to_binary_block(iterable, header, datatype, is_big_endian) - if sys.version >= '3': - return bytes(header, 'ascii') + struct.pack(fullfmt, *iterable) - else: - return str(header) + struct.pack(fullfmt, *iterable) + +def to_hp_block(iterable, datatype='f', is_big_endian=False): + """Convert an iterable of numbers into a block of data in the HP format. + + :param iterable: an iterable of numbers. + :param datatype: the format string for a single element. See struct module. + :param is_big_endian: boolean indicating endianess. + :return: IEEE block. + :rtype: bytes + """ + + array_length = len(iterable) + element_length = struct.calcsize(datatype) + data_length = array_length * element_length + + header = b'#A' + (int_to_bytes(data_length, 2, + 'big' if is_big_endian else 'little')) + + return to_binary_block(iterable, header, datatype, is_big_endian) def get_system_details(backends=True): @@ -530,7 +676,7 @@ machine_types = { 0x0200: 'IA64', 0x0266: 'MIPS16', 0x0284: 'ALPHA64', - #0x0284: 'AXP64', # same + # 0x0284: 'AXP64', # same 0x0366: 'MIPSFPU', 0x0466: 'MIPSFPU16', 0x0520: 'TRICORE', @@ -573,7 +719,7 @@ def get_arch(filename): return 64, else: return () - elif not this_platform in ('linux2', 'linux3', 'linux', 'darwin'): + elif this_platform not in ('linux2', 'linux3', 'linux', 'darwin'): raise OSError('') out = check_output(["file", filename], stderr=subprocess.STDOUT) @@ -15,16 +15,15 @@ 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() +a2 = read('AUTHORS').decode('utf-8') +a3 = read('CHANGES').decode('utf-8') +a1 = read('README').decode('utf-8') -long_description = '\n\n'.join([read('README'), - read('AUTHORS'), - read('CHANGES')]) +long_description = '\n\n'.join([a1,a2,a3]) __doc__ = long_description @@ -32,21 +31,19 @@ requirements = [] if sys.version_info < (3, 4): requirements.append('enum34') -if sys.version_info < (2, 7): - requirements.append('unittest2') - setup(name='PyVISA', - description='Python VISA bindings for GPIB, RS232, and USB instruments', - version='1.8', + description='Python VISA bindings for GPIB, RS232, TCPIP and USB instruments', + version='1.9.0', long_description=long_description, author='Torsten Bronger, Gregor Thalhammer', author_email='bronger@physik.rwth-aachen.de', maintainer='Hernan E. Grecco', maintainer_email='hernan.grecco@gmail.com', - url='https://github.com/hgrecco/pyvisa', + url='https://github.com/pyvisa/pyvisa', test_suite='pyvisa.testsuite.testsuite', keywords='VISA GPIB USB serial RS232 measurement acquisition', license='MIT License', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', install_requires=requirements, classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -59,11 +56,12 @@ setup(name='PyVISA', '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', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', ], packages=['pyvisa', 'pyvisa.compat', 'pyvisa.ctwrapper', @@ -71,6 +69,9 @@ setup(name='PyVISA', 'pyvisa.thirdparty', 'pyvisa.testsuite'], platforms="Linux, Windows,Mac", + entry_points={'console_scripts': + ['pyvisa-shell=visa:visa_shell', + 'pyvisa-info=visa:visa_info']}, py_modules=['visa'], use_2to3=False, zip_safe=False) @@ -1,9 +1,9 @@ [tox] -envlist = py26,py27,py32,py33 +envlist = py27,py34,py35,py36 [testenv] deps= - pytest + pytest sphinx mock @@ -21,19 +21,35 @@ from pyvisa.errors import (Error, VisaIOError, VisaIOWarning, VisaTypeError, # This is needed to registry all resources. from pyvisa.resources import Resource -if __name__ == '__main__': +def visa_main(command=None): import argparse parser = argparse.ArgumentParser(description='PyVISA command-line utilities') - subparsers = parser.add_subparsers(title='command', dest='command') - info_parser = subparsers.add_parser('info', help='print information to diagnose PyVISA') + parser.add_argument('--backend', '-b', dest='backend', action='store', default=None, + help='backend to be used (default: ni)') + + if not command: + subparsers = parser.add_subparsers(title='command', dest='command') - console_parser = subparsers.add_parser('shell', help='start the PyVISA console') + info_parser = subparsers.add_parser('info', help='print information to diagnose PyVISA') + + console_parser = subparsers.add_parser('shell', help='start the PyVISA console') args = parser.parse_args() + if command: + args.command = command if args.command == 'info': from pyvisa import util util.get_debug_info() elif args.command == 'shell': from pyvisa import shell - shell.main() + shell.main('@' + args.backend if args.backend else '') + +def visa_shell(): + visa_main('shell') + +def visa_info(): + visa_main('info') + +if __name__ == '__main__': + visa_main() |