From 24693a6c44b1606b229c231d2aaa403a6096d766 Mon Sep 17 00:00:00 2001 From: Ruben Undheim Date: Sun, 13 Sep 2015 21:13:05 +0200 Subject: Import pyvisa-py_0.2.orig.tar.gz [dgit import orig pyvisa-py_0.2.orig.tar.gz] --- .gitignore | 15 + .travis.yml | 21 + AUTHORS | 16 + CHANGES | 36 ++ LICENSE | 21 + MANIFEST.in | 5 + README | 62 +++ docs/Makefile | 153 ++++++++ docs/_static/logo-full.jpg | Bin 0 -> 10898 bytes docs/conf.py | 307 +++++++++++++++ docs/index.rst | 133 +++++++ docs/make.bat | 190 +++++++++ pyvisa-py/__init__.py | 29 ++ pyvisa-py/common.py | 81 ++++ pyvisa-py/gpib.py | 258 +++++++++++++ pyvisa-py/highlevel.py | 318 +++++++++++++++ pyvisa-py/protocols/__init__.py | 13 + pyvisa-py/protocols/rpc.py | 833 ++++++++++++++++++++++++++++++++++++++++ pyvisa-py/protocols/usbraw.py | 89 +++++ pyvisa-py/protocols/usbtmc.py | 317 +++++++++++++++ pyvisa-py/protocols/usbutil.py | 252 ++++++++++++ pyvisa-py/protocols/vxi11.py | 290 ++++++++++++++ pyvisa-py/serial.py | 372 ++++++++++++++++++ pyvisa-py/sessions.py | 333 ++++++++++++++++ pyvisa-py/tcpip.py | 480 +++++++++++++++++++++++ pyvisa-py/testsuite/__init__.py | 32 ++ pyvisa-py/usb.py | 214 +++++++++++ setup.cfg | 4 + setup.py | 71 ++++ 29 files changed, 4945 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 AUTHORS create mode 100644 CHANGES create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README create mode 100644 docs/Makefile create mode 100644 docs/_static/logo-full.jpg create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 pyvisa-py/__init__.py create mode 100644 pyvisa-py/common.py create mode 100644 pyvisa-py/gpib.py create mode 100644 pyvisa-py/highlevel.py create mode 100644 pyvisa-py/protocols/__init__.py create mode 100644 pyvisa-py/protocols/rpc.py create mode 100644 pyvisa-py/protocols/usbraw.py create mode 100644 pyvisa-py/protocols/usbtmc.py create mode 100644 pyvisa-py/protocols/usbutil.py create mode 100644 pyvisa-py/protocols/vxi11.py create mode 100644 pyvisa-py/serial.py create mode 100644 pyvisa-py/sessions.py create mode 100644 pyvisa-py/tcpip.py create mode 100644 pyvisa-py/testsuite/__init__.py create mode 100644 pyvisa-py/usb.py create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac02c9e --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*~ +__pycache__ +*egg-info* +*.pyc +.DS_Store +docs/_build/ +.idea +build/ +dist/ +MANIFEST +.tox +.coveragerc +# WebDAV file system cache files +.DAV/ +_test/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2b115fb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: python + +python: + - "2.6" + - "2.7" + - "3.2" + - "3.3" + - "3.4" + +install: + - if [ $TRAVIS_PYTHON_VERSION == '2.6' ]; then pip install unittest2; fi + - pip install coverage 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 + - coverage combine + - coverage report -m + +after_script: + - coveralls --verbose diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..ea3490d --- /dev/null +++ b/AUTHORS @@ -0,0 +1,16 @@ +pyvisa-py is written and maintained by Hernan E. Grecco . + + +Other contributors, listed alphabetically, are: + +* Alex Forencich +* Alexander Bessman +* Colin Marquardt +* Lance McCulley +* Martin Ritter +* Sebastian Held +* Thomas Kopp <20.kopp@gmail.com> +* Thorsten Liebig +* Tobias Müller + +(If you think that your name belongs here, please let the maintainer know) diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..70cc2d5 --- /dev/null +++ b/CHANGES @@ -0,0 +1,36 @@ +PyVISA-py Changelog +=================== + + +0.2 (2015-08-25) +---------------- + +- Added support for TCPIP Socket. + (Issue #38, thanks Thorsten Liebig) +- Added support for GPIB INSTR using linux-gpib. + (Issue #24, thanks bessman) +- Added support for USB RAW. + (Issue #18, kopp) +- Better error reporting when pyusb or pyserial is missing. +- Fixed logging of unicode strings. + (Issue #54) +- Fixed timeout in SerialSession. + (Issue #44) +- Moved resource name parsing to PyVISA. +- VXI11 protocol performance enhancement. + (thanks alexforencich) +- Improved pyusb importing. +- Fixed large binary reads in TCPIP. +- Added backend information to logger. +- Use pyvisa compat/struct.py for python < 2.7.8 + (thanks Martin Ritter) + + + +0.1 (2015-02-08) +---------------- + +- Initial release. Preliminary support for: + - USB INSTR + - TCPIP INSTR + - ASRL INSTR diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..91b7fc2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2014 PyVISA-py Authors and contributors. See AUTHORS + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..8385f90 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include README AUTHORS CHANGES LICENSE +recursive-include pyvisa-py * +recursive-include docs * +prune docs/_build +global-exclude *.pyc *~ .DS_Store *__pycache__* *.pyo diff --git a/README b/README new file mode 100644 index 0000000..88404e7 --- /dev/null +++ b/README @@ -0,0 +1,62 @@ +PyVISA-py +========= + +A PyVISA backend that implements a large part of the "Virtual Instrument Software +Architecture" (VISA_) in pure Python (with the help of some nice cross platform +libraries python packages!). + + +Description +----------- + +PyVISA started as wrapper for the NI-VISA library and therefore you need to install +National Instruments VISA library in your system. This works most of the time, +for most people. But NI-VISA is a proprietary library that only works on certain +systems. That is when PyVISA-py jumps in. + +Starting from version 1.6, PyVISA allows to use different backends. These backends can be +dynamically loaded. PyVISA-py is one of such backends. It implements most of the methods +for Message Based communication (Serial/USB/GPIB/Ethernet) using Python and some well developed, +easy to deploy and cross platform libraries + +.. _VISA: http://www.ivifoundation.org/Downloads/Specifications.htm + + +VISA and Python +--------------- + +Python has a couple of features that make it very interesting for measurement controlling: + +- Python is an easy-to-learn scripting language with short development cycles. +- It represents a high abstraction level, which perfectly blends with the abstraction + level of measurement programs. +- It has a very rich set of native libraries, including numerical and plotting modules for + data analysis and visualisation. +- A large set of books (in many languages) and on-line publications is available. + + +Requirements +------------ + +- Python (tested with 2.6 and 2.7, 3.2+) +- PyVISA 1.6+ + +Optionally +- PySerial (to interface with Serial instruments) +- PyUSB (to interface with USB instruments) +- linux-gpib (to interface with gpib instruments, only on linux) + + +Installation +-------------- + +Using pip: + + $ pip install pyvisa-py + + + +Documentation +-------------- + +The documentation can be read online at https://pyvisa-py.readthedocs.org diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..fe28973 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyvisa-py.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyvisa-py.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/pyvisa-py" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyvisa-py" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/_static/logo-full.jpg b/docs/_static/logo-full.jpg new file mode 100644 index 0000000..763a12a Binary files /dev/null and b/docs/_static/logo-full.jpg differ diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..86a548c --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,307 @@ +# -*- coding: utf-8 -*- +# +# PyVISA documentation build configuration file +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import os +import sys +import pkg_resources +import datetime + + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'sphinx.ext.mathjax'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +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 +copyright = '%s, %s' % (this_year, author) + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'sphinx_rtd_theme' + +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +if not on_rtd: # only import and set the theme if we're building docs locally + try: + import sphinx_rtd_theme + except ImportError: + print('\n\nTheme not found. Please install Sphinx Read The Docs Themes using:\n\n' + ' pip install sphinx_rtd_theme\n') + sys.exit(1) + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] +#html_theme_path = ['_themes'] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} +html_sidebars = { + 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], + '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', + 'sourcelink.html', 'searchbox.html'] +} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pyvisa-pytdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'pyvisa-py.tex', 'PyVISA Documentation', + 'PyVISA Authors', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'pyvisa-py', 'PyVISA Documentation', + ['PyVISA Authors'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'PyVISA', 'PyVISA Documentation', + 'PyVISA Authors', 'PyVISA', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + + +# -- Options for Epub output --------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project +epub_author = author +epub_publisher = author +epub_copyright = copyright + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'python': ('http://docs.python.org/2', None)} diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..862532f --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,133 @@ +:orphan: + + +PyVISA-py: Pure Python backend for PyVISA +========================================= + +.. image:: _static/logo-full.jpg + :alt: PyVISA + + +PyVISA-py is a backend for PyVISA_. It implements most of the methods +for Message Based communication (Serial/USB/GPIB/Ethernet) using Python +and some well developed, easy to deploy and cross platform libraries. + +You can select the PyVISA-py backend using **@py** when instantiating the +visa Resource Manager: + + >>> import visa + >>> rm = visa.ResourceManager('@py') + >>> rm.list_resources() + ('USB0::0x1AB1::0x0588::DS1K00005888::INSTR') + >>> inst = rm.open_resource('USB0::0x1AB1::0x0588::DS1K00005888::INSTR') + >>> print(inst.query("*IDN?")) + + +That's all! Except for **@py**, the code is exactly what you would write to +using the NI-VISA backend for PyVISA. + + +Installation +============ + +Just run the following command in your console: + + pip install pyvisa-py + + +You can report a problem or ask for features in the `issue tracker`_. +Or get the code in GitHub_. + + +FAQ +=== + + +Which libraries are used by PyVISA-py? +-------------------------------------- + +It depends on the interface type. For **ASRL** and **USB** we use PySerial_ and PyUSB_ +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`_. + + +If I only need **TCPIP**, do I need to install PySerial and PyUSB? +------------------------------------------------------------------ + +No. Libraries are loaded on demand. + + +How do I know if PyVISA-py is properly installed? +------------------------------------------------- + +Using the pyvisa information tool. Run in your console:: + + python -m visa info + +You will get info about PyVISA, the installed backends and their options. + + +Which resource types are supported? +----------------------------------- + +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. + + +Why are you developing this? +---------------------------- + +The `National Instruments's VISA`_ is a proprietary library that only works on certain systems. +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. + + +Why putting PyVISA in the middle? +--------------------------------- + +Because it allows you to change the backend easily without changing your application. +In other projects we implemented classes to call USBTMC devices without PyVISA. +But this leads to code duplication or an adapter class in your code. +By using PyVISA as a frontend to many backends, we abstract these things +from higher level applications. + + + +.. _PySerial: http://pyserial.sourceforge.net/ +.. _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 +.. _`National Instruments's VISA`: http://ni.com/visa/ +.. _`LibreVISA`: http://www.librevisa.org/ +.. _`issue tracker`: https://github.com/hgrecco/pyvisa-py/issues +.. _`linux-gpib`: http://linux-gpib.sourceforge.net/ + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..89c160c --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyvisa-py.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyvisa-py.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/pyvisa-py/__init__.py b/pyvisa-py/__init__.py new file mode 100644 index 0000000..d78e7f7 --- /dev/null +++ b/pyvisa-py/__init__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-py + ~~~~~~~~~ + + Pure Python backend for PyVISA. + + + :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 + + +import pkg_resources + +__version__ = "unknown" +try: # pragma: no cover + __version__ = pkg_resources.get_distribution('pyvisa-py').version +except: # pragma: no cover + pass # we seem to have a local copy without any repository control or installed without setuptools + # so the reported version will be __unknown__ + + +from .highlevel import PyVisaLibrary + +WRAPPER_CLASS = PyVisaLibrary + diff --git a/pyvisa-py/common.py b/pyvisa-py/common.py new file mode 100644 index 0000000..1f91543 --- /dev/null +++ b/pyvisa-py/common.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-sim.common + ~~~~~~~~~~~~~~~~~ + + Common code. + + :copyright: 2014 by PyVISA-sim Authors, see AUTHORS for more details. + :license: MIT, see LICENSE for more details. +""" + +from __future__ import division, unicode_literals, print_function, absolute_import + +import logging +import sys + +from pyvisa import logger + +logger = logging.LoggerAdapter(logger, {'backend': 'py'}) + + +class MockInterface(object): + + def __init__(self, resource_name): + self.resource_name = resource_name + + +class NamedObject(object): + """A class to construct named sentinels. + """ + + def __init__(self, name): + self.name = name + + def __repr__(self): + return '<%s>' % self.name + + __str__ = __repr__ + + +if sys.version >= '3': + def iter_bytes(data, mask=None, send_end=False): + + if send_end and mask is None: + raise ValueError('send_end requires a valid mask.') + + if mask is None: + for d in data[:]: + yield bytes([d]) + + else: + for d in data[:-1]: + yield bytes([d & ~mask]) + + if send_end: + yield bytes([data[-1] | ~mask]) + else: + yield bytes([data[-1] & ~mask]) + + int_to_byte = lambda val: bytes([val]) + last_int = lambda val: val[-1] +else: + def iter_bytes(data, mask=None, send_end=False): + + if send_end and mask is None: + raise ValueError('send_end requires a valid mask.') + + if mask is None: + for d in data[:]: + yield d + else: + for d in data[:-1]: + yield chr(ord(d) & ~mask) + + if send_end: + yield chr(ord(data[-1]) | ~mask) + else: + yield chr(ord(data[-1]) & ~mask) + + int_to_byte = chr + last_int = lambda val: ord(val[-1]) diff --git a/pyvisa-py/gpib.py b/pyvisa-py/gpib.py new file mode 100644 index 0000000..ef495d0 --- /dev/null +++ b/pyvisa-py/gpib.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-py.gpib + ~~~~~~~~~~~~~~ + + GPIB Session implementation using linux-gpib. + + + :copyright: 2015 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 bisect import bisect + +from pyvisa import constants, logger + +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 + + +def _find_listeners(): + """Find GPIB listeners. + """ + for i in range(31): + if gpib.listener(BOARD, i) and gpib.ask(BOARD, 1) != i: + yield i + + +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) + + +# TODO: Check board indices other than 0. +BOARD = 0 +# TODO: Check secondary addresses. +@Session.register(constants.InterfaceType.gpib, 'INSTR') +class GPIBSession(Session): + """A GPIB Session that uses linux-gpib to do the low level communication. + """ + + @staticmethod + 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): + + # 0x3 is the hexadecimal reference to the IbaTMO (timeout) configuration + # option in linux-gpib. + return TIMETABLE[self.interface.ask(3)] + + @timeout.setter + def timeout(self, 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 + available value. Values greater than the largest available timout value + will instead be rounded down. The available timeout values are: + 0 Never timeout. + 1 10 microseconds + 2 30 microseconds + 3 100 microseconds + 4 300 microseconds + 5 1 millisecond + 6 3 milliseconds + 7 10 milliseconds + 8 30 milliseconds + 9 100 milliseconds + 10 300 milliseconds + 11 1 second + 12 3 seconds + 13 10 seconds + 14 30 seconds + 15 100 seconds + 16 300 seconds + 17 1000 seconds + """ + self.interface.timeout(bisect(TIMETABLE, value)) + + def close(self): + gpib.close(self.handle) + + def read(self, count): + """Reads data from device or interface synchronously. + + Corresponds to viRead function of the VISA library. + + :param count: Number of bytes to be read. + :return: data read, return value of the library call. + :rtype: bytes, constants.StatusCode + """ + + # 0x2000 = 8192 = END + checker = lambda current: self.interface.ibsta() & 8192 + + reader = lambda: self.interface.read(1) + + return self._read(reader, count, checker, False, None, False, gpib.GpibError) + + def write(self, data): + """Writes data to device or interface synchronously. + + Corresponds to viWrite function of the VISA library. + + :param data: data to be written. + :type data: bytes + :return: Number of bytes actually transferred, return value of the library call. + :rtype: int, VISAStatus + """ + + logger.debug('GPIB.write %r' % data) + + try: + self.interface.write(data) + + return SUCCESS + + except gpib.GpibError: + # 0x4000 = 16384 = TIMO + if self.interface.ibsta() & 16384: + return 0, StatusCode.error_timeout + else: + return 0, StatusCode.error_system_error + + def _get_attribute(self, attribute): + """Get the value for a given VISA attribute for this 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. + :rtype: (unicode | str | list | int, VISAStatus) + """ + + if attribute == constants.VI_ATTR_GPIB_READDR_EN: + # IbaREADDR 0x6 + # Setting has no effect in linux-gpib. + return self.interface.ask(6), SUCCESS + + elif attribute == constants.VI_ATTR_GPIB_PRIMARY_ADDR: + # IbaPAD 0x1 + return self.interface.ask(1), 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 + else: + return constants.VI_NO_SEC_ADDR, SUCCESS + + elif attribute == constants.VI_ATTR_GPIB_REN_STATE: + # I have no idea how to implement this. + raise NotImplementedError + + elif attribute == constants.VI_ATTR_GPIB_UNADDR_EN: + # IbaUnAddr 0x1b + if self.interface.ask(27): + return constants.VI_TRUE, SUCCESS + else: + return constants.VI_FALSE, SUCCESS + + elif attribute == constants.VI_ATTR_SEND_END_EN: + # IbaEndBitIsNormal 0x1a + if self.interface.ask(26): + return constants.VI_TRUE, SUCCESS + else: + return constants.VI_FALSE, SUCCESS + + elif attribute == constants.VI_ATTR_INTF_NUM: + # IbaBNA 0x200 + return self.interface.ask(512), SUCCESS + + elif attribute == constants.VI_ATTR_INTF_TYPE: + return constants.InterfaceType.gpib, SUCCESS + + raise UnknownAttribute(attribute) + + def _set_attribute(self, attribute, attribute_state): + """Sets the state of an attribute. + + 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. + :return: return value of the library call. + :rtype: VISAStatus + """ + + if attribute == constants.VI_ATTR_GPIB_READDR_EN: + # IbcREADDR 0x6 + # Setting has no effect in linux-gpib. + if isinstance(attribute_state, int): + self.interface.config(6, attribute_state) + return SUCCESS + else: + return StatusCode.error_nonsupported_attribute_state + + elif attribute == constants.VI_ATTR_GPIB_PRIMARY_ADDR: + # IbcPAD 0x1 + if isinstance(attribute_state, int) and 0 <= attribute_state <= 30: + self.interface.config(1, attribute_state) + return SUCCESS + else: + return StatusCode.error_nonsupported_attribute_state + + elif attribute == constants.VI_ATTR_GPIB_SECONDARY_ADDR: + # IbcSAD 0x2 + # Add 0x60 because National Instruments. + if isinstance(attribute_state, int) and 0 <= attribute_state <= 30: + if self.interface.ask(2): + self.interface.config(2, attribute_state + 96) + return SUCCESS + else: + return StatusCode.error_nonsupported_attribute + else: + return StatusCode.error_nonsupported_attribute_state + + elif attribute == constants.VI_ATTR_GPIB_UNADDR_EN: + # IbcUnAddr 0x1b + try: + self.interface.config(27, attribute_state) + return SUCCESS + except gpib.GpibError: + return StatusCode.error_nonsupported_attribute_state + + elif attribute == constants.VI_ATTR_SEND_END_EN: + # IbcEndBitIsNormal 0x1a + if isinstance(attribute_state, int): + self.interface.config(26, attribute_state) + return SUCCESS + else: + return StatusCode.error_nonsupported_attribute_state + + raise UnknownAttribute(attribute) + diff --git a/pyvisa-py/highlevel.py b/pyvisa-py/highlevel.py new file mode 100644 index 0000000..1753e01 --- /dev/null +++ b/pyvisa-py/highlevel.py @@ -0,0 +1,318 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-py.highlevel + ~~~~~~~~~~~~~~~~~~~ + + Highlevel wrapper of the VISA Library. + + + :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 + +import warnings + +import random + +from pyvisa import constants, errors, highlevel, rname +from pyvisa.compat import integer_types, OrderedDict + +from . import sessions +from .common import logger + + +class PyVisaLibrary(highlevel.VisaLibraryBase): + """A pure Python backend for PyVISA. + + The object is basically a dispatcher with some common functions implemented. + + When a new resource object is requested to pyvisa, the library creates a Session object + (that knows how to perform low-level communication operations) associated with a session handle + (a number, usually refered just as session). + + A call to a library function is handled by PyVisaLibrary if it involves a resource agnosting + function or dispatched to the correct session object (obtained from the session id). + + Importantly, the user is unaware of this. PyVisaLibrary behaves for the user just as NIVisaLibrary. + """ + + # Try to import packages implementing lower level functionality. + try: + from .serial import SerialSession + logger.debug('SerialSession was correctly imported.') + except ImportError 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: + logger.debug('USBSession and USBRawSession were not imported %s.' % e) + + try: + from .tcpip import TCPIPSession + logger.debug('TCPIPSession was correctly imported.') + except ImportError 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: + logger.debug('GPIBSession was not imported %s.' % e) + + @classmethod + def get_session_classes(cls): + return sessions.Session._session_classes + + @classmethod + def iter_session_classes_issues(cls): + return sessions.Session.iter_session_classes_issues() + + @staticmethod + def get_debug_info(): + """Return a list of lines with backend info. + """ + from . import __version__ + d = OrderedDict() + d['Version'] = '%s' % __version__ + + for key, val in PyVisaLibrary.get_session_classes().items(): + key_name = '%s %s' % (key[0].name.upper(), key[1]) + try: + d[key_name] = getattr(val, 'session_issue').split('\n') + except AttributeError: + d[key_name] = 'Available ' + val.get_low_level_info() + + return d + + def _init(self): + + #: map session handle to session object. + #: dict[int, session.Session] + self.sessions = {} + + def _register(self, obj): + """Creates a random but unique session handle for a session object, + register it in the sessions dictionary and return the value + + :param obj: a session object. + :return: session handle + :rtype: int + """ + session = None + + while session is None or session in self.sessions: + session = random.randint(1000000, 9999999) + + self.sessions[session] = obj + return session + + def _return_handler(self, ret_value, func, arguments): + """Check return values for errors and warnings. + + TODO: THIS IS JUST COPIED PASTED FROM NIVisaLibrary. + Needs to be adapted. + """ + + logger.debug('%s%s -> %r', + func.__name__, _args_to_str(arguments), ret_value, + extra=self._logging_extra) + + try: + ret_value = constants.StatusCode(ret_value) + except ValueError: + pass + + self._last_status = ret_value + + # The first argument of almost all registered visa functions is a session. + # We store the error code per session + session = None + if func.__name__ not in ('viFindNext', ): + try: + session = arguments[0] + except KeyError: + raise Exception('Function %r does not seem to be a valid ' + 'visa function (len args %d)' % (func, len(arguments))) + + # Functions that use the first parameter to get a session value. + if func.__name__ in ('viOpenDefaultRM', ): + # noinspection PyProtectedMember + session = session._obj.value + + if isinstance(session, integer_types): + self._last_status_in_session[session] = ret_value + else: + # Functions that might or might have a session in the first argument. + if func.__name__ not in ('viClose', 'viGetAttribute', 'viSetAttribute', 'viStatusDesc'): + raise Exception('Function %r does not seem to be a valid ' + 'visa function (type args[0] %r)' % (func, type(session))) + + if ret_value < 0: + raise errors.VisaIOError(ret_value) + + if ret_value in self.issue_warning_on: + if session and ret_value not in self._ignore_warning_in_session[session]: + warnings.warn(errors.VisaIOWarning(ret_value), stacklevel=2) + + return ret_value + + # noinspection PyShadowingBuiltins + def open(self, session, resource_name, + 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. + + :param session: Resource Manager session (should always be a session returned from open_default_resource_manager()). + :param resource_name: Unique symbolic name of a resource. + :param access_mode: Specifies the mode by which the resource is to be accessed. (constants.AccessModes) + :param open_timeout: Specifies the maximum time period (in milliseconds) that this operation waits + before returning an error. + :return: Unique logical identifier reference to a session, return value of the library call. + :rtype: session, VISAStatus + """ + + try: + open_timeout = int(open_timeout) + except ValueError: + raise ValueError('open_timeout (%r) must be an integer (or compatible type)' % open_timeout) + + try: + parsed = rname.parse_resource_name(resource_name) + except rname.InvalidResourceName: + return 0, constants.StatusCode.error_invalid_resource_name + + cls = sessions.Session.get_session_class(parsed.interface_type_const, parsed.resource_class) + + sess = cls(session, resource_name, parsed) + + return self._register(sess), constants.StatusCode.success + + def close(self, session): + """Closes the specified session, event, or find list. + + Corresponds to viClose function of the VISA library. + + :param session: Unique logical identifier to a session, event, or find list. + :return: return value of the library call. + :rtype: VISAStatus + """ + try: + sess = self.sessions[session] + if sess is not self: + sess.close() + except KeyError: + return constants.StatusCode.error_invalid_object + + def open_default_resource_manager(self): + """This function returns a session to the Default Resource Manager resource. + + Corresponds to viOpenDefaultRM function of the VISA library. + + :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 + + def list_resources(self, session, query='?*::INSTR'): + """Returns a tuple of all connected devices matching query. + + :param query: regular expression used to match devices. + """ + + # For each session type, ask for the list of connected resources and + # merge them into a single list. + + resources = sum([st.list_resources() + for key, st in sessions.Session.iter_valid_session_classes()], []) + + resources = rname.filter(resources, query) + + if resources: + return resources + + raise errors.VisaIOError(errors.StatusCode.error_resource_not_found.value) + + def read(self, session, count): + """Reads data from device or interface synchronously. + + Corresponds to viRead function of the VISA library. + + :param session: Unique logical identifier to a session. + :param count: Number of bytes to be read. + :return: data read, return value of the library call. + :rtype: bytes, VISAStatus + """ + + # from the session handle, dispatch to the read method of the session object. + try: + return self.sessions[session].read(count) + except KeyError: + return constants.StatusCode.error_invalid_object + + def write(self, session, data): + """Writes data to device or interface synchronously. + + Corresponds to viWrite function of the VISA library. + + :param session: Unique logical identifier to a session. + :param data: data to be written. + :type data: str + :return: Number of bytes actually transferred, return value of the library call. + :rtype: int, VISAStatus + """ + + # from the session handle, dispatch to the write method of the session object. + try: + return self.sessions[session].write(data) + except KeyError: + return constants.StatusCode.error_invalid_object + + def get_attribute(self, session, attribute): + """Retrieves the state of an attribute. + + Corresponds to viGetAttribute function of the VISA library. + + :param session: Unique logical identifier to a session, event, or find list. + :param attribute: Resource attribute for which the state query is made (see Attributes.*) + :return: The state of the queried attribute for a specified resource, return value of the library call. + :rtype: unicode | str | list | int, VISAStatus + """ + try: + sess = self.sessions[session] + except KeyError: + return None, constants.StatusCode.error_invalid_object + + return sess.get_attribute(attribute) + + def set_attribute(self, session, attribute, attribute_state): + """Sets the state of an attribute. + + Corresponds to viSetAttribute function of the VISA library. + + :param session: Unique logical identifier to a session. + :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 + """ + + try: + sess = self.sessions[session] + except KeyError: + return constants.StatusCode.error_invalid_object + + return sess.set_attribute(attribute, attribute_state) + + def disable_event(self, session, event_type, mechanism): + # TODO: implement this for GPIB finalization + pass + + def discard_events(self, session, event_type, mechanism): + # TODO: implement this for GPIB finalization + pass + diff --git a/pyvisa-py/protocols/__init__.py b/pyvisa-py/protocols/__init__.py new file mode 100644 index 0000000..b437348 --- /dev/null +++ b/pyvisa-py/protocols/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-py.protocols + ~~~~~~~~~~~~~~~~~~~ + + Implements protocols on top of lower level libraries to talk to instruments. + + + :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 diff --git a/pyvisa-py/protocols/rpc.py b/pyvisa-py/protocols/rpc.py new file mode 100644 index 0000000..66fe51c --- /dev/null +++ b/pyvisa-py/protocols/rpc.py @@ -0,0 +1,833 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-py.protocols.rpc + ~~~~~~~~~~~~~~~~~~~~~~~ + + Sun RPC version 2 -- RFC1057 + + This file is drawn from Python's RPC demo, updated for python 3. + + XXX There should be separate exceptions for the various reasons why + XXX an RPC can fail, rather than using RuntimeError for everything + + 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 + + + :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 + +import sys +import enum +import xdrlib +import socket + +from pyvisa.compat import struct + +from ..common import logger + +#: Version of the protocol +RPCVERSION = 2 + + +class MessagegType(enum.IntEnum): + call = 0 + reply = 1 + + +class AuthorizationFlavor(enum.IntEnum): + + null = 0 + unix = 1 + short = 2 + des = 3 + + +class ReplyStatus(enum.IntEnum): + + accepted = 0 + denied = 1 + + +class AcceptStatus(enum.IntEnum): + + #: RPC executed successfully + success = 0 + + #: remote hasn't exported program + program_unavailable = 1 + + #: remote can't support version + program_mismatch = 2 + + #: program can't support procedure + procedure_unavailable = 3 + + #: procedure can't decode params + garbage_args = 4 + + +class RejectStatus(enum.IntEnum): + + #: RPC version number != 2 + rpc_mismatch = 0 + + #: remote can't authenticate caller + auth_error = 1 + + +class AuthStatus(enum.IntEnum): + ok = 0 + + #: bad credentials (seal broken) + bad_credentials = 1 + + #: client must begin new session + rejected_credentials = 2 + + #: bad verifier (seal broken) + bad_verifier = 3 + + #: verifier expired or replayed + rejected_verifier = 4 + + #: rejected for security reasons + too_weak = 5 + + +# Exceptions +class RPCError(Exception): + pass + + +class RPCBadFormat(RPCError): + pass + + +class RPCBadVersion(RPCError): + pass + + +class RPCGarbageArgs(RPCError): + pass + + +class RPCUnpackError(RPCError): + pass + + +def make_auth_null(): + return b'' + + +class Packer(xdrlib.Packer): + + def pack_auth(self, auth): + flavor, stuff = auth + self.pack_enum(flavor) + self.pack_opaque(stuff) + + def pack_auth_unix(self, stamp, machinename, uid, gid, gids): + self.pack_uint(stamp) + self.pack_string(machinename) + self.pack_uint(uid) + self.pack_uint(gid) + self.pack_uint(len(gids)) + for i in gids: + self.pack_uint(i) + + def pack_callheader(self, xid, prog, vers, proc, cred, verf): + self.pack_uint(xid) + self.pack_enum(MessagegType.call) + self.pack_uint(RPCVERSION) + self.pack_uint(prog) + self.pack_uint(vers) + self.pack_uint(proc) + self.pack_auth(cred) + self.pack_auth(verf) + # Caller must add procedure-specific part of call + + def pack_replyheader(self, xid, verf): + self.pack_uint(xid) + self.pack_enum(MessagegType.reply) + self.pack_uint(ReplyStatus.accepted) + self.pack_auth(verf) + self.pack_enum(AcceptStatus.success) + # Caller must add procedure-specific part of reply + + +class Unpacker(xdrlib.Unpacker): + + def unpack_auth(self): + flavor = self.unpack_enum() + stuff = self.unpack_opaque() + return flavor, stuff + + def unpack_callheader(self): + xid = self.unpack_uint() + temp = self.unpack_enum() + if temp != MessagegType.call: + raise RPCBadFormat('no CALL but %r' % (temp,)) + temp = self.unpack_uint() + if temp != RPCVERSION: + raise RPCBadVersion('bad RPC version %r' % (temp,)) + prog = self.unpack_uint() + vers = self.unpack_uint() + proc = self.unpack_uint() + cred = self.unpack_auth() + verf = self.unpack_auth() + return xid, prog, vers, proc, cred, verf + # Caller must add procedure-specific part of call + + def unpack_replyheader(self): + xid = self.unpack_uint() + mtype = self.unpack_enum() + if mtype != MessagegType.reply: + raise RPCUnpackError('no reply but %r' % (mtype,)) + stat = self.unpack_enum() + if stat == ReplyStatus.denied: + stat = self.unpack_enum() + if stat == RejectStatus.rpc_mismatch: + low = self.unpack_uint() + high = self.unpack_uint() + raise RPCUnpackError('denied: rpc_mismatch: %r' % ((low, high),)) + if stat == RejectStatus.auth_error: + stat = self.unpack_uint() + raise RPCUnpackError('denied: auth_error: %r' % (stat,)) + raise RPCUnpackError('denied: %r' % (stat,)) + if stat != ReplyStatus.accepted: + raise RPCUnpackError('Neither denied nor accepted: %r' % (stat,)) + verf = self.unpack_auth() + stat = self.unpack_enum() + if stat == AcceptStatus.program_unavailable: + raise RPCUnpackError('call failed: program_unavailable') + if stat == AcceptStatus.program_mismatch: + low = self.unpack_uint() + high = self.unpack_uint() + 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: + raise RPCGarbageArgs + if stat != AcceptStatus.success: + raise RPCUnpackError('call failed: %r' % (stat,)) + return xid, verf + # Caller must get procedure-specific part of reply + + +class Client(object): + """Common base class for clients. + """ + + def __init__(self, host, prog, vers, port): + self.host = host + self.prog = prog + self.vers = vers + self.port = port + self.lastxid = 0 # XXX should be more random? + self.cred = None + self.verf = None + + 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) + + if pack_func is None and args is not None: + raise TypeError('non-null args with null pack_func') + self.start_call(proc) + if pack_func: + pack_func(args) + self.do_call() + if unpack_func: + result = unpack_func() + else: + result = None + self.unpacker.done() + return result + + def start_call(self, proc): + # Don't override this + self.lastxid = xid = 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) + + def do_call(self): + # This MUST be overridden + raise RPCError('do_call not defined') + + def mkcred(self): + # Override this to use more powerful credentials + if self.cred is None: + self.cred = (AuthorizationFlavor.null, make_auth_null()) + return self.cred + + def mkverf(self): + # Override this to use a more powerful verifier + if self.verf is None: + self.verf = (AuthorizationFlavor.null, make_auth_null()) + return self.verf + + def call_0(self): + # Procedure 0 is always like this + return self.make_call(0, None, None, None) + + +# Record-Marking standard support + +def sendfrag(sock, last, frag): + x = len(frag) + if last: + x = x | 0x80000000 + header = struct.pack(">I", x) + 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 recvrecord(sock): + record = b'' + last = 0 + while not last: + last, frag = recvfrag(sock) + record = record + frag + + logger.debug('Received record through %s: %r', sock, record) + + return record + + +class RawTCPClient(Client): + """Client using TCP 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) + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((self.host, self.port)) + + 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) + 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)) + + +# Client using UDP to a specific port + +class RawUDPClient(Client): + 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) + 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() + + 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) + if self.sock not in r: + count = count - 1 + if count < 0: + raise RPCError('timeout') + if timeout < 25: + timeout = timeout * 2 + self.sock.send(call) + continue + reply = self.sock.recv(BUFSIZE) + u = self.unpacker + u.reset(reply) + xid, verf = u.unpack_replyheader() + if xid != self.lastxid: + continue + break + + +class RawBroadcastUDPClient(RawUDPClient): + """Client using UDP broadcast to a specific port. + """ + + def __init__(self, bcastaddr, prog, vers, port): + 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) + + def set_reply_handler(self, reply_handler): + self.reply_handler = reply_handler + + def set_timeout(self, timeout): + self.timeout = timeout # Use None for infinite timeout + + def make_call(self, 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') + self.start_call(proc) + if pack_func: + 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: + def dummy(): + pass + unpack_func = dummy + while 1: + r, w, x = [self.sock], [], [] + if select: + if self.timeout is None: + r, w, x = select(r, w, x) + else: + r, w, x = select(r, w, x, self.timeout) + if self.sock not in r: + break + reply, fromaddr = self.sock.recvfrom(BUFSIZE) + u = self.unpacker + u.reset(reply) + xid, verf = u.unpack_replyheader() + if xid != self.lastxid: + continue + reply = unpack_func() + self.unpacker.done() + replies.append((reply, fromaddr)) + if self.reply_handler: + self.reply_handler(reply, fromaddr) + return replies + + +# Port mapper interface + +# Program number, version and port number +PMAP_PROG = 100000 +PMAP_VERS = 2 +PMAP_PORT = 111 + + +class PortMapperVersion(enum.IntEnum): + #: (void) -> void + null = 0 + #: (mapping) -> bool + set = 1 + #: (mapping) -> bool + unset = 2 + #: (mapping) -> unsigned int + get_port = 3 + #: (void) -> pmaplist + dump = 4 + #: (call_args) -> call_result + call_it = 5 + +# A mapping is (prog, vers, prot, port) and prot is one of: + +IPPROTO_TCP = 6 +IPPROTO_UDP = 17 + +# A pmaplist is a variable-length list of mappings, as follows: +# either (1, mapping, pmaplist) or (0). + +# A call_args is (prog, vers, proc, args) where args is opaque; +# a call_result is (port, res) where res is opaque. + + +class PortMapperPacker(Packer): + + def pack_mapping(self, mapping): + prog, vers, prot, port = mapping + self.pack_uint(prog) + self.pack_uint(vers) + self.pack_uint(prot) + self.pack_uint(port) + + def pack_pmaplist(self, list): + self.pack_list(list, self.pack_mapping) + + def pack_call_args(self, ca): + prog, vers, proc, args = ca + self.pack_uint(prog) + self.pack_uint(vers) + self.pack_uint(proc) + self.pack_opaque(args) + + +class PortMapperUnpacker(Unpacker): + + def unpack_mapping(self): + prog = self.unpack_uint() + vers = self.unpack_uint() + prot = self.unpack_uint() + port = self.unpack_uint() + return prog, vers, prot, port + + def unpack_pmaplist(self): + return self.unpack_list(self.unpack_mapping) + + def unpack_call_result(self): + port = self.unpack_uint() + res = self.unpack_opaque() + return port, res + + +class PartialPortMapperClient(object): + + def __init__(self): + self.packer = PortMapperPacker() + self.unpacker = PortMapperUnpacker('') + + def set(self, mapping): + return self.make_call(PortMapperVersion.set, mapping, + self.packer.pack_mapping, + self.unpacker.unpack_uint) + + def unset(self, mapping): + return self.make_call(PortMapperVersion.unset, mapping, + self.packer.pack_mapping, + self.unpacker.unpack_uint) + + def get_port(self, mapping): + return self.make_call(PortMapperVersion.get_port, mapping, + self.packer.pack_mapping, + self.unpacker.unpack_uint) + + def dump(self): + return self.make_call(PortMapperVersion.dump, None, + None, + self.unpacker.unpack_pmaplist) + + def callit(self, ca): + return self.make_call(PortMapperVersion.call_it, ca, + self.packer.pack_call_args, + self.unpacker.unpack_call_result) + + +class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient): + + def __init__(self, host): + RawTCPClient.__init__(self, host, PMAP_PROG, PMAP_VERS, PMAP_PORT) + PartialPortMapperClient.__init__(self) + + +class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient): + + def __init__(self, host): + RawUDPClient.__init__(self, host, PMAP_PROG, PMAP_VERS, PMAP_PORT) + PartialPortMapperClient.__init__(self) + + +class BroadcastUDPPortMapperClient(PartialPortMapperClient, RawBroadcastUDPClient): + + def __init__(self, bcastaddr): + 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) + 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) + + +class UDPClient(RawUDPClient): + """A UDP Client that find their server through the Port mapper + """ + def __init__(self, host, prog, vers): + pmap = UDPPortMapperClient(host) + port = pmap.get_port((prog, vers, IPPROTO_UDP, 0)) + pmap.close() + if port == 0: + raise RPCError('program not registered') + RawUDPClient.__init__(self, host, prog, vers, port) + + +class BroadcastUDPClient(Client): + """A Broadcast UDP Client that find their server through the Port mapper + """ + + def __init__(self, bcastaddr, prog, vers): + self.pmap = BroadcastUDPPortMapperClient(bcastaddr) + self.pmap.set_reply_handler(self.my_reply_handler) + self.prog = prog + self.vers = vers + self.user_reply_handler = None + self.addpackers() + + def close(self): + self.pmap.close() + + def set_reply_handler(self, reply_handler): + self.user_reply_handler = reply_handler + + def set_timeout(self, timeout): + self.pmap.set_timeout(timeout) + + def my_reply_handler(self, reply, fromaddr): + port, res = reply + self.unpacker.reset(res) + result = self.unpack_func() + self.unpacker.done() + self.replies.append((result, fromaddr)) + if self.user_reply_handler is not None: + self.user_reply_handler(result, fromaddr) + + def make_call(self, proc, args, pack_func, unpack_func): + self.packer.reset() + if pack_func: + pack_func(args) + if unpack_func is None: + def dummy(): pass + self.unpack_func = dummy + else: + 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)) + return self.replies + + +# Server classes + +# These are not symmetric to the Client classes +# XXX No attempt is made to provide authorization hooks yet + +class Server(object): + + def __init__(self, host, prog, vers, port): + self.host = host # Should normally be '' for default interface + self.prog = prog + self.vers = vers + self.port = port # Should normally be 0 for random port + self.port = port + self.addpackers() + + def register(self): + mapping = self.prog, self.vers, self.prot, self.port + p = TCPPortMapperClient(self.host) + if not p.set(mapping): + raise RPCError('register failed') + + def unregister(self): + mapping = self.prog, self.vers, self.prot, self.port + p = TCPPortMapperClient(self.host) + if not p.unset(mapping): + raise RPCError('unregister failed') + + def handle(self, call): + # Don't use unpack_header but parse the header piecewise + # XXX I have no idea if I am using the right error responses! + self.unpacker.reset(call) + self.packer.reset() + xid = self.unpacker.unpack_uint() + self.packer.pack_uint(xid) + temp = self.unpacker.unpack_enum() + if temp != MessagegType.call: + return None # Not worthy of a reply + self.packer.pack_uint(MessagegType.reply) + temp = self.unpacker.unpack_uint() + if temp != RPCVERSION: + self.packer.pack_uint(ReplyStatus.denied) + self.packer.pack_uint(RejectStatus.rpc_mismatch) + self.packer.pack_uint(RPCVERSION) + self.packer.pack_uint(RPCVERSION) + return self.packer.get_buf() + self.packer.pack_uint(ReplyStatus.accepted) + self.packer.pack_auth((AuthorizationFlavor.null, make_auth_null())) + prog = self.unpacker.unpack_uint() + if prog != self.prog: + self.packer.pack_uint(AcceptStatus.program_unavailable) + return self.packer.get_buf() + vers = self.unpacker.unpack_uint() + if vers != self.vers: + self.packer.pack_uint(AcceptStatus.program_mismatch) + self.packer.pack_uint(self.vers) + self.packer.pack_uint(self.vers) + return self.packer.get_buf() + proc = self.unpacker.unpack_uint() + methname = 'handle_' + repr(proc) + try: + meth = getattr(self, methname) + except AttributeError: + self.packer.pack_uint(AcceptStatus.procedure_unavailable) + return self.packer.get_buf() + cred = self.unpacker.unpack_auth() + verf = self.unpacker.unpack_auth() + try: + meth() # Unpack args, call turn_around(), pack reply + except (EOFError, RPCGarbageArgs): + # Too few or too many arguments + self.packer.reset() + self.packer.pack_uint(xid) + self.packer.pack_uint(MessagegType.reply) + self.packer.pack_uint(ReplyStatus.accepted) + self.packer.pack_auth((AuthorizationFlavor.null, make_auth_null())) + self.packer.pack_uint(AcceptStatus.garbage_args) + return self.packer.get_buf() + + def turn_around(self): + try: + self.unpacker.done() + except RuntimeError: + raise RPCGarbageArgs + self.packer.pack_uint(AcceptStatus.success) + + def handle_0(self): + # Handle NULL message + self.turn_around() + + def addpackers(self): + # Override this to use derived classes from Packer/Unpacker + self.packer = Packer() + self.unpacker = Unpacker('') + + +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 + self.sock.bind((self.host, self.port)) + + def loop(self): + self.sock.listen(0) + while 1: + self.session(self.sock.accept()) + + def session(self, connection): + sock, (host, port) = connection + while 1: + try: + call = recvrecord(sock) + except EOFError: + break + except socket.error: + logger.exception('socket error: %r', sys.exc_info()[0]) + break + reply = self.handle(call) + if reply is not None: + sendrecord(sock, reply) + + def forkingloop(self): + # Like loop but uses forksession() + self.sock.listen(0) + while 1: + self.forksession(self.sock.accept()) + + def forksession(self, connection): + # Like session but forks off a subprocess + import os + # Wait for deceased children + try: + while 1: + pid, sts = os.waitpid(0, 1) + except os.error: + pass + pid = None + try: + pid = os.fork() + if pid: # Parent + connection[0].close() + return + # Child + self.session(connection) + finally: + # Make sure we don't fall through in the parent + if pid == 0: + os._exit(0) + + +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() + + def session(self): + call, host_port = self.sock.recvfrom(8192) + reply = self.handle(call) + if reply is not None: + self.sock.sendto(reply, host_port) diff --git a/pyvisa-py/protocols/usbraw.py b/pyvisa-py/protocols/usbraw.py new file mode 100644 index 0000000..0e06c4a --- /dev/null +++ b/pyvisa-py/protocols/usbraw.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-py.protocols.usbraw + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Implements Session to control USB Raw 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. + + :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 .usbtmc import USBRaw as USBRaw + +import usb + +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): + """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 find_devices(vendor, product, serial_number, is_usbraw, **kwargs) + + +class USBRawDevice(USBRaw): + + RECV_CHUNK = 1024 ** 2 + + find_devices = staticmethod(find_raw_devices) + + + def __init__(self, vendor=None, product=None, serial_number=None, **kwargs): + super(USBRawDevice, self).__init__(vendor, product, serial_number, **kwargs) + + if not (self.usb_recv_ep and self.usb_send_ep): + raise ValueError("USBRAW device must have both Bulk-In and Bulk-out endpoints.") + + def write(self, data): + """Send raw bytes to the instrument. + + :param data: bytes to be sent to the instrument + :type data: bytes + """ + + begin, end, size = 0, 0, len(data) + bytes_sent = 0 + + raw_write = super(USBRawDevice, self).write + + while not end > size: + begin = end + end = begin + self.RECV_CHUNK + bytes_sent += raw_write(data[begin:end]) + + return bytes_sent + + + def read(self, size): + """Read raw bytes from the instrument. + + :param size: amount of bytes to be sent to the instrument + :type size: integer + :return: received bytes + :return type: bytes + """ + + raw_read = super(USBRawDevice, self).read + raw_write = super(USBRawDevice, self).write + + received = b'' + + while not len(received) >= size: + resp = raw_read(self.RECV_CHUNK) + + received += resp + + return received diff --git a/pyvisa-py/protocols/usbtmc.py b/pyvisa-py/protocols/usbtmc.py new file mode 100644 index 0000000..20c53d2 --- /dev/null +++ b/pyvisa-py/protocols/usbtmc.py @@ -0,0 +1,317 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-py.protocols.usbtmc + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Implements Session to control USBTMC instruments + + 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. + + :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 + +import enum +from pyvisa.compat import struct +import time +from collections import namedtuple + +import usb + +from .usbutil import find_devices, find_interfaces, find_endpoint, usb_find_desc + +import sys + +if sys.version_info < (3, 2): + def array_to_bytes(arr): + return arr.tostring() +else: + def array_to_bytes(arr): + return arr.tobytes() + + +class MsgID(enum.IntEnum): + """From USB-TMC table2 + """ + dev_dep_msg_out = 1 + request_dev_dep_msg_in = 2 + dev_dep_msg_in = 2 + vendor_specific_out = 126 + request_vendor_specific_in = 127 + vendor_specific_in = 127 + trigger = 128 # for USB488 + + +class Request(enum.IntEnum): + initiate_abort_bulk_out = 1 + check_abort_bulk_out_status = 2 + initiate_abort_bulk_in = 3 + check_abort_bulk_in_status = 4 + initiate_clear = 5 + check_clear_status = 6 + get_capabilities = 7 + indicator_pulse = 64 + + +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 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. + """ + + @staticmethod + def build_array(btag, eom, chunk): + size = len(chunk) + return struct.pack('BBBx', MsgID.dev_dep_msg_out, btag, ~btag & 0xFF) + \ + struct.pack(" 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)) + + self.usb_dev, other = devices[0], devices[1:] + + try: + if self.usb_dev.is_kernel_driver_active(0): + self.usb_dev.detach_kernel_driver(0) + except (usb.core.USBError, NotImplementedError) as e: + pass + + try: + self.usb_dev.set_configuration() + except usb.core.USBError as e: + raise Exception('failed to set configuration\n %s' % e) + + try: + self.usb_dev.set_interface_altsetting() + except usb.core.USBError as e: + pass + + 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) + + def _find_interface(self, dev, setting): + return self.usb_dev.get_active_configuration()[self.INTERFACE] + + def _find_endpoints(self, interface, setting): + recv, send = setting + if recv is None: + 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) + else: + send = usb_find_desc(interface, bEndpointAddress=send) + + return recv, send + + def write(self, data): + """Send raw bytes to the instrument. + + :param data: bytes to be sent to the instrument + :type data: bytes + """ + + try: + return self.usb_send_ep.write(data) + except usb.core.USBError as e: + raise ValueError(str(e)) + + def read(self, size): + """Receive raw bytes to the instrument. + + :param size: number of bytes to receive + :return: received bytes + :return type: bytes + """ + + if size <= 0: + size = 1 + + data = array_to_bytes(self.usb_recv_ep.read(size, self.timeout)) + + return data + + def close(self): + return usb.util.dispose_resources(self.usb_dev) + + +class USBTMC(USBRaw): + + RECV_CHUNK = 1024 ** 2 + + find_devices = staticmethod(find_tmc_devices) + + 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_dev.reset() + self.usb_dev.set_configuration() + + time.sleep(0.01) + + self._get_capabilities() + + 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.") + + 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) + + def _find_interface(self, dev, setting): + interfaces = find_interfaces(dev, bInterfaceClass=0xFE, bInterfaceSubClass=3) + if not interfaces: + raise ValueError('USB TMC interface not found.') + elif len(interfaces) > 1: + pass + + return interfaces[0] + + def write(self, data): + """Send raw bytes to the instrument. + + :param data: bytes to be sent to the instrument + :type data: bytes + """ + + begin, end, size = 0, 0, len(data) + bytes_sent = 0 + + raw_write = super(USBTMC, self).write + + while not end > size: + begin, end = end, begin + self.RECV_CHUNK + + self._btag = (self._btag % 255) + 1 + + 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 + + eom = False + + raw_read = super(USBTMC, self).read + raw_write = super(USBTMC, self).write + + received = b'' + + while not eom: + self._btag = (self._btag % 255) + 1 + + req = BulkInMessage.build_array(self._btag, recv_chunk, None) + + raw_write(req) + + resp = raw_read(recv_chunk) + + response = BulkInMessage.from_bytes(resp) + + received += response.data + + eom = response.transfer_attributes & 1 + + return received diff --git a/pyvisa-py/protocols/usbutil.py b/pyvisa-py/protocols/usbutil.py new file mode 100644 index 0000000..44791df --- /dev/null +++ b/pyvisa-py/protocols/usbutil.py @@ -0,0 +1,252 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-py.usb + ~~~~~~~~~~~~~ + + Serial Session implementation using PyUSB. + + See the following link for more information about USB. + + http://www.beyondlogic.org/usbnutshell/usb5.shtml + + This file is an offspring of the Lantz Project. + + :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 fnmatch import fnmatch + +import usb +from usb.util import (get_string as usb_get_string, + find_descriptor as usb_find_desc) + + +ClassCodes = { + 0x00: ('Device', 'Use class information in the Interface Descriptors'), + 0x01: ('Interface', 'Audio'), + 0x02: ('Both', 'Communications and CDC Control'), + 0x03: ('Interface', 'HID (Human Interface Device)'), + 0x05: ('Interface', 'Physical'), + 0x06: ('Interface', 'Image'), + 0x07: ('Interface', 'Printer'), + 0x08: ('Interface', 'Mass Storage'), + 0x09: ('Device', 'Hub'), + 0x0A: ('Interface', 'CDC-Data'), + 0x0B: ('Interface', 'Smart Card'), + 0x0D: ('Interface', 'Content Security'), + 0x0E: ('Interface', 'Video'), + 0x0F: ('Interface', 'Personal Healthcare'), + 0x10: ('Interface', 'Audio/Video Devices'), + 0xDC: ('Both', 'Diagnostic Device'), + 0xE0: ('Interface', 'Wireless Controller'), + 0xEF: ('Both', 'Miscellaneous'), + 0xFE: ('Interface', 'Application Specific'), + 0xFF: ('Both', 'Vendor Specific') +} + +# None is 0xxx +AllCodes = { + (0x00, 0x00, 0x00): 'Use class code info from Interface Descriptors', + (0x01, None, None): 'Audio device', + (0x02, None, None): 'Communication device class', + (0x03, None, None): 'HID device class', + (0x05, None, None): 'Physical device class', + (0x06, 0x01, 0x01): 'Still Imaging device', + (0x07, None, None): 'Printer device', + (0x08, None, None): 'Mass Storage device', + (0x09, 0x00, 0x00): 'Full speed Hub', + (0x09, 0x00, 0x01): 'Hi-speed hub with single TT', + (0x09, 0x00, 0x02): 'Hi-speed hub with multiple TTs', + (0x0A, None, None): 'CDC data device', + (0x0B, None, None): 'Smart Card device', + (0x0D, 0x00, 0x00): 'Content Security device', + (0x0E, None, None): 'Video device', + (0x0F, None, None): 'Personal Healthcare device', + (0x10, 0x01, 0x00): 'Control Interface', + (0x10, 0x02, 0x00): 'Data Video Streaming Interface', + (0x10, 0x03, 0x00): 'VData Audio Streaming Interface', + (0xDC, 0x01, 0x01): 'USB2 Compliance Device', + (0xE0, 0x01, 0x01): 'Bluetooth Programming Interface.', + (0xE0, 0x01, 0x02): 'UWB Radio Control Interface.', + (0xE0, 0x01, 0x03): 'Remote NDIS', + (0xE0, 0x01, 0x04): 'Bluetooth AMP Controller.', + (0xE0, 0x2, 0x01): 'Host Wire Adapter Control/Data interface.', + (0xE0, 0x2, 0x02): 'Device Wire Adapter Control/Data interface.', + (0xE0, 0x2, 0x03): 'Device Wire Adapter Isochronous interface.', + (0xEF, 0x01, 0x01): 'Active Sync device.', + (0xEF, 0x01, 0x02): 'Palm Sync. This class code can be used in either ' + 'Device or Interface Descriptors.', + (0xEF, 0x02, 0x01): 'Interface Association Descriptor.', + (0xEF, 0x02, 0x02): 'Wire Adapter Multifunction Peripheral programming interface.', + (0xEF, 0x03, 0x01): 'Cable Based Association Framework.', + (0xEF, 0x04, 0x01): 'RNDIS over Ethernet. Connecting a host to the Internet via ' + 'Ethernet mobile device. The device appears to the host as an' + 'Ethernet gateway device. This class code may only be used in ' + 'Interface Descriptors.', + (0xEF, 0x04, 0x02): 'RNDIS over WiFi. Connecting a host to the Internet via WiFi ' + 'enabled mobile device. The device represents itself to the host' + 'as an 802.11 compliant network device. This class code may only' + 'be used in Interface Descriptors.', + (0xEF, 0x04, 0x03): 'RNDIS over WiMAX. Connecting a host to the Internet via WiMAX ' + 'enabled mobile device. The device is represented to the host ' + 'as an 802.16 network device. This class code may only be used ' + 'in Interface Descriptors.', + (0xEF, 0x04, 0x04): 'RNDIS over WWAN. Connecting a host to the Internet via a device ' + 'using mobile broadband, i.e. WWAN (GSM/CDMA). This class code may ' + 'only be used in Interface Descriptors.', + (0xEF, 0x04, 0x05): 'RNDIS for Raw IPv4. Connecting a host to the Internet using raw ' + 'IPv4 via non-Ethernet mobile device. Devices that provide raw ' + 'IPv4, not in an Ethernet packet, may use this form to in lieu of ' + 'other stock types. ' + 'This class code may only be used in Interface Descriptors.', + (0xEF, 0x04, 0x06): 'RNDIS for Raw IPv6. Connecting a host to the Internet using raw ' + 'IPv6 via non-Ethernet mobile device. Devices that provide raw ' + 'IPv6, not in an Ethernet packet, may use this form to in lieu of ' + 'other stock types. ' + 'This class code may only be used in Interface Descriptors.', + (0xEF, 0x04, 0x07): 'RNDIS for GPRS. Connecting a host to the Internet over GPRS mobile ' + 'device using the device‚Äôs cellular radio.', + (0xEF, 0x05, 0x00): 'USB3 Vision Control Interface', + (0xEF, 0x05, 0x01): 'USB3 Vision Event Interface', + (0xEF, 0x05, 0x02): 'USB3 Vision Streaming Interface', + (0xFE, 0x01, 0x01): 'Device Firmware Upgrade.', + (0xFE, 0x02, 0x00): 'IRDA Bridge device.', + (0xFE, 0x03, 0x00): 'USB Test and Measurement Device.', + (0xFE, 0x03, 0x01): 'USB Test and Measurement Device conforming to the USBTMC USB488 Subclass', + (0xFF, None, None): 'Vendor specific' +} + + +class LantzUSBTimeoutError(usb.core.USBError): + pass + + +def ep_attributes(ep): + c = ep.bmAttributes + attrs = [] + tp = c & usb.ENDPOINT_TYPE_MASK + if tp == usb.ENDPOINT_TYPE_CONTROL: + attrs.append('Control') + elif tp == usb.ENDPOINT_TYPE_ISOCHRONOUS: + attrs.append('Isochronous') + elif tp == usb.ENDPOINT_TYPE_BULK: + attrs.append('Bulk') + elif tp == usb.ENDPOINT_TYPE_INTERRUPT: + attrs.append('Interrupt') + + sync = (c & 12) >> 2 + if sync == 0: + attrs.append('No sync') + elif sync == 1: + attrs.append('Async') + elif sync == 2: + attrs.append('Adaptive') + elif sync == 3: + attrs.append('Sync') + usage = (c & 48) >> 4 + if usage == 0: + attrs.append('Data endpoint') + elif usage == 1: + attrs.append('Feedback endpoint') + elif usage == 2: + attrs.append('Subordinate Feedback endpoint') + elif usage == 3: + attrs.append('Reserved') + + return ', '.join(attrs) + + +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. + + :param vendor: name or id of the vendor (manufacturer) + :param product: name or id of the product + :param serial_number: serial number. + :param custom_match: callable returning True or False that takes a device as only input. + :param kwargs: other properties to match. See usb.core.find + :return: + """ + kwargs = kwargs or {} + attrs = {} + if isinstance(vendor, str): + attrs['manufacturer'] = vendor + elif vendor is not None: + kwargs['idVendor'] = vendor + + if isinstance(product, str): + attrs['product'] = product + elif product is not None: + kwargs['idProduct'] = product + + if serial_number: + attrs['serial_number'] = str(serial_number) + + if attrs: + def cm(dev): + if custom_match is not None and not custom_match(dev): + return False + for attr, pattern in attrs.items(): + if not fnmatch(getattr(dev, attr).lower(), pattern.lower()): + return False + return True + else: + cm = custom_match + + return usb.core.find(find_all=True, custom_match=cm, **kwargs) + + +def find_interfaces(device, **kwargs): + """ + :param device: + :return: + """ + interfaces = [] + try: + for cfg in device: + try: + interfaces.extend(usb_find_desc(cfg, find_all=True, **kwargs)) + except: + pass + except: + pass + return interfaces + + +def find_endpoint(interface, direction, type): + ep = usb_find_desc(interface, custom_match= + lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == direction and + usb.util.endpoint_type(e.bmAttributes) == type + ) + return ep + + +def _patch_endpoint(ep, log_func=print): + _read = ep.read + _write = ep.write + + def new_read(*args, **kwargs): + log_func('---') + log_func('reading from {}'.format(ep.bEndpointAddress)) + log_func('args: {}'.format(args)) + log_func('kwargs: {}'.format(kwargs)) + ret = _read(*args, **kwargs) + log_func('returned', ret) + log_func('---') + return ret + + def new_write(*args, **kwargs): + log_func('---') + log_func('writing to {}'.format(ep.bEndpointAddress)) + log_func('args: {}'.format(args)) + log_func('kwargs: {}'.format(kwargs)) + ret = _write(*args, **kwargs) + log_func('returned', ret) + log_func('---') + return ret + ep.read = new_read + ep.write = new_write diff --git a/pyvisa-py/protocols/vxi11.py b/pyvisa-py/protocols/vxi11.py new file mode 100644 index 0000000..0026b06 --- /dev/null +++ b/pyvisa-py/protocols/vxi11.py @@ -0,0 +1,290 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-py.protocols.vxi11 + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Implements a VX11 Session using Python Standard Library. + + Based on Python Sun RPC Demo and Alex Forencich python-vx11 + + This file is an offspring of the Lantz project. + + :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 + +import enum + +from . import rpc + + +# VXI-11 RPC constants + +# Device async +DEVICE_ASYNC_PROG = 0x0607b0 +DEVICE_ASYNC_VERS = 1 +DEVICE_ABORT = 1 + +# Device core +DEVICE_CORE_PROG = 0x0607af +DEVICE_CORE_VERS = 1 +CREATE_LINK = 10 +DEVICE_WRITE = 11 +DEVICE_READ = 12 +DEVICE_READSTB = 13 +DEVICE_TRIGGER = 14 +DEVICE_CLEAR = 15 +DEVICE_REMOTE = 16 +DEVICE_LOCAL = 17 +DEVICE_LOCK = 18 +DEVICE_UNLOCK = 19 +DEVICE_ENABLE_SRQ = 20 +DEVICE_DOCMD = 22 +DESTROY_LINK = 23 +CREATE_INTR_CHAN = 25 +DESTROY_INTR_CHAN = 26 + +# Device intr +DEVICE_INTR_PROG = 0x0607b1 +DEVICE_INTR_VERS = 1 +DEVICE_INTR_SRQ = 30 + + +# Error states +class ErrorCodes(enum.IntEnum): + no_error = 0 + syntax_error = 1 + device_not_accessible = 3 + invalid_link_identifier = 4 + parameter_error = 5 + channel_not_established = 6 + operation_not_supported = 8 + out_of_resources = 9 + device_locked_by_another_link = 11 + no_lock_held_by_this_link = 12 + io_timeout = 15 + io_error = 17 + abort = 23 + channel_already_established = 29 + +# Flags +OP_FLAG_WAIT_BLOCK = 1 +OP_FLAG_END = 8 +OP_FLAG_TERMCHAR_SET = 128 + +RX_REQCNT = 1 +RX_CHR = 2 +RX_END = 4 + + +class Vxi11Error(Exception): + pass + + +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) + self.pack_uint(io_timeout) + 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) + self.pack_uint(request_size) + self.pack_uint(io_timeout) + 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) + self.pack_uint(host_port) + 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) + self.pack_bool(enable) + 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 + self.pack_int(link) + self.pack_int(flags) + self.pack_uint(io_timeout) + self.pack_uint(lock_timeout) + self.pack_int(cmd) + self.pack_bool(network_order) + self.pack_int(datasize) + self.pack_opaque(data_in) + + +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() + return error, data_out + + +class CoreClient(rpc.TCPClient): + + def __init__(self, host): + self.packer = Vxi11Packer() + self.unpacker = Vxi11Unpacker('') + super(CoreClient, self).__init__(host, DEVICE_CORE_PROG, DEVICE_CORE_VERS) + + def create_link(self, id, lock_device, lock_timeout, name): + params = (id, lock_device, lock_timeout, name) + return self.make_call(CREATE_LINK, params, + self.packer.pack_create_link_parms, + self.unpacker.unpack_create_link_resp) + + 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) + + def device_read_stb(self, link, flags, lock_timeout, io_timeout): + params = (link, flags, lock_timeout, io_timeout) + return self.make_call(DEVICE_READSTB, params, + self.packer.pack_device_generic_parms, + self.unpacker.unpack_device_read_stb_resp) + + def device_trigger(self, link, flags, lock_timeout, io_timeout): + params = (link, flags, lock_timeout, io_timeout) + return self.make_call(DEVICE_TRIGGER, params, + self.packer.pack_device_generic_parms, + self.unpacker.unpack_device_error) + + def device_clear(self, link, flags, lock_timeout, io_timeout): + params = (link, flags, lock_timeout, io_timeout) + return self.make_call(DEVICE_CLEAR, params, + self.packer.pack_device_generic_parms, + self.unpacker.unpack_device_error) + + def device_remote(self, link, flags, lock_timeout, io_timeout): + params = (link, flags, lock_timeout, io_timeout) + return self.make_call(DEVICE_REMOTE, params, + self.packer.pack_device_generic_parms, + self.unpacker.unpack_device_error) + + def device_local(self, link, flags, lock_timeout, io_timeout): + params = (link, flags, lock_timeout, io_timeout) + return self.make_call(DEVICE_LOCAL, params, + self.packer.pack_device_generic_parms, + self.unpacker.unpack_device_error) + + def device_lock(self, link, flags, lock_timeout): + params = (link, flags, lock_timeout) + return self.make_call(DEVICE_LOCK, params, + self.packer.pack_device_lock_parms, + self.unpacker.unpack_device_error) + + def device_unlock(self, link): + return self.make_call(DEVICE_UNLOCK, link, + self.packer.pack_device_link, + self.unpacker.unpack_device_error) + + def device_enable_srq(self, link, enable, handle): + params = (link, enable, handle) + return self.make_call(DEVICE_ENABLE_SRQ, params, + 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) + return self.make_call(DEVICE_DOCMD, params, + self.packer.pack_device_docmd_parms, + self.unpacker.unpack_device_docmd_resp) + + def destroy_link(self, link): + 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): + 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 new file mode 100644 index 0000000..36ff2ab --- /dev/null +++ b/pyvisa-py/serial.py @@ -0,0 +1,372 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-py.serial + ~~~~~~~~~~~~~~~~ + + Serial Session implementation using PySerial. + + + :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 pyvisa import constants, attributes, logger + +from .sessions import Session, UnknownAttribute +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) + + raise + + +def to_state(boolean_input): + """Convert a boolean input into a LineState value + """ + if boolean_input: + return constants.LineState.asserted + return constants.LineState.unasserted + + +StatusCode = constants.StatusCode +SUCCESS = StatusCode.success +SerialTermination = constants.SerialTermination + + +@Session.register(constants.InterfaceType.asrl, 'INSTR') +class SerialSession(Session): + """A serial Session that uses PySerial to do the low level communication. + """ + + @staticmethod + def list_resources(): + return ['ASRL%s::INSTR' % port[0] for port in comports()] + + @classmethod + def get_low_level_info(cls): + try: + ver = serial.VERSION + except AttributeError: + ver = 'N/A' + + return 'via PySerial (%s)' % ver + + def after_parsing(self): + if 'mock' in self.parsed: + cls = self.parsed.mock + else: + cls = Serial + + self.interface = cls(port=self.parsed.board, timeout=2000, writeTimeout=2000) + + for name in 'ASRL_END_IN,ASRL_END_OUT,SEND_END_EN,TERMCHAR,' \ + 'TERMCHAR_EN,SUPPRESS_END_EN'.split(','): + 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. + + self.interface.timeout = value + self.interface.writeTimeout = value + + def close(self): + self.interface.close() + + def read(self, count): + """Reads data from device or interface synchronously. + + Corresponds to viRead function of the VISA library. + + :param count: Number of bytes to be read. + :return: data read, return value of the library call. + :rtype: bytes, constants.StatusCode + """ + + end_in, _ = self.get_attribute(constants.VI_ATTR_ASRL_END_IN) + suppress_end_en, _ = self.get_attribute(constants.VI_ATTR_SUPPRESS_END_EN) + + reader = lambda: self.interface.read(1) + + if end_in == SerialTermination.none: + checker = lambda current: False + + elif end_in == SerialTermination.last_bit: + mask = 2 ** self.interface.bytesize + checker = lambda current: bool(common.last_int(current) & mask) + + elif end_in == SerialTermination.termination_char: + end_char, _ = self.get_attribute(constants.VI_ATTR_TERMCHAR) + + checker = lambda current: common.last_int(current) == end_char + + else: + raise ValueError('Unknown value for VI_ATTR_ASRL_END_IN: %s' % end_in) + + return self._read(reader, count, checker, suppress_end_en, None, False, + serial.SerialTimeoutException) + + def write(self, data): + """Writes data to device or interface synchronously. + + Corresponds to viWrite function of the VISA library. + + :param data: data to be written. + :type data: bytes + :return: Number of bytes actually transferred, return value of the library call. + :rtype: int, VISAStatus + """ + logger.debug('Serial.write %r' % data) + # TODO: How to deal with VI_ATTR_TERMCHAR_EN + end_out, _ = self.get_attribute(constants.VI_ATTR_ASRL_END_OUT) + send_end, _ = self.get_attribute(constants.VI_ATTR_SEND_END_EN) + + try: + # We need to wrap data in common.iter_bytes to Provide Python 2 and 3 compatibility + + if end_out in (SerialTermination.none, SerialTermination.termination_break): + data = common.iter_bytes(data) + + elif end_out == SerialTermination.last_bit: + last_bit, _ = self.get_attribute(constants.VI_ATTR_ASRL_DATA_BITS) + mask = 1 << (last_bit - 1) + data = common.iter_bytes(data, mask, send_end) + + elif end_out == SerialTermination.termination_char: + term_char, _ = self.get_attribute(constants.VI_ATTR_TERMCHAR) + data = common.iter_bytes(data + common.int_to_byte(term_char)) + + else: + raise ValueError('Unknown value for VI_ATTR_ASRL_END_OUT: %s' % end_out) + + count = 0 + for d in data: + count += self.interface.write(d) + + if end_out == SerialTermination.termination_break: + logger.debug('Serial.sendBreak') + self.interface.sendBreak() + + return count, constants.StatusCode.success + + except serial.SerialTimeoutException: + return 0, StatusCode.error_timeout + + def _get_attribute(self, attribute): + """Get the value for a given VISA attribute for this 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. + :rtype: (unicode | str | list | int, VISAStatus) + """ + + if attribute == constants.VI_ATTR_ASRL_ALLOW_TRANSMIT: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_AVAIL_NUM: + return self.interface.inWaiting(), SUCCESS + + elif attribute == constants.VI_ATTR_ASRL_BAUD: + return self.interface.baudrate, SUCCESS + + elif attribute == constants.VI_ATTR_ASRL_BREAK_LEN: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_BREAK_STATE: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_CONNECTED: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_CTS_STATE: + return to_state(self.interface.getCTS()), SUCCESS + + elif attribute == constants.VI_ATTR_ASRL_DATA_BITS: + return self.interface.bytesize, SUCCESS + + elif attribute == constants.VI_ATTR_ASRL_DCD_STATE: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_DISCARD_NULL: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_DSR_STATE: + return to_state(self.interface.getDSR()), SUCCESS + + elif attribute == constants.VI_ATTR_ASRL_DTR_STATE: + raise NotImplementedError + + 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 + + elif attribute == constants.VI_ATTR_ASRL_PARITY: + parity = self.interface.parity + if parity == serial.PARITY_NONE: + return constants.Parity.none, SUCCESS + elif parity == serial.PARITY_EVEN: + return constants.Parity.even, SUCCESS + elif parity == serial.PARITY_ODD: + return constants.Parity.odd, SUCCESS + elif parity == serial.PARITY_MARK: + return constants.Parity.mark, SUCCESS + elif parity == serial.PARITY_SPACE: + return constants.Parity.space, SUCCESS + + raise Exception('Unknown parity value: %r' % parity) + + elif attribute == constants.VI_ATTR_ASRL_RI_STATE: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_RTS_STATE: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_STOP_BITS: + bits = self.interface.stopbits + if bits == serial.STOPBITS_ONE: + return constants.StopBits.one, SUCCESS + elif bits == serial.STOPBITS_ONE_POINT_FIVE: + return constants.StopBits.one_and_a_half, SUCCESS + elif bits == serial.STOPBITS_TWO: + return constants.StopBits.two, SUCCESS + + raise Exception('Unknown bits value: %r' % bits) + + elif attribute == constants.VI_ATTR_ASRL_XOFF_CHAR: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_INTF_TYPE: + return constants.InterfaceType.asrl, SUCCESS + + raise UnknownAttribute(attribute) + + def _set_attribute(self, attribute, attribute_state): + """Sets the state of an attribute. + + 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. + :return: return value of the library call. + :rtype: VISAStatus + """ + + if attribute == constants.VI_ATTR_ASRL_ALLOW_TRANSMIT: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_BAUD: + self.interface.baudrate = attribute_state + return StatusCode.success + + elif attribute == constants.VI_ATTR_ASRL_BREAK_LEN: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_BREAK_STATE: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_CONNECTED: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_DATA_BITS: + self.interface.bytesize = attribute_state + return StatusCode.success + + elif attribute == constants.VI_ATTR_ASRL_DCD_STATE: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_DISCARD_NULL: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_DSR_STATE: + return to_state(self.interface.getDSR()) + + elif attribute == constants.VI_ATTR_ASRL_DTR_STATE: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_FLOW_CNTRL: + if not isinstance(attribute_state, int): + return StatusCode.error_nonsupported_attribute_state + + if not 0 < attribute_state < 8: + return StatusCode.error_nonsupported_attribute_state + + try: + self.interface.xonxoff = attribute_state & constants.VI_ASRL_FLOW_XON_XOFF + self.interface.rtscts = attribute_state & constants.VI_ASRL_FLOW_RTS_CTS + self.interface.dsrdtr = attribute_state & constants.VI_ASRL_FLOW_DTR_DSR + return StatusCode.success + except: + return StatusCode.error_nonsupported_attribute_state + + elif attribute == constants.VI_ATTR_ASRL_PARITY: + if attribute_state == constants.Parity.none: + self.interface.parity = serial.PARITY_NONE + return StatusCode.success + + elif attribute_state == constants.Parity.even: + self.interface.parity = serial.PARITY_EVEN + return StatusCode.success + + elif attribute_state == constants.Parity.odd: + self.interface.parity = serial.PARITY_ODD + return StatusCode.success + + elif attribute_state == serial.PARITY_MARK: + self.interface.parity = serial.PARITY_MARK + return StatusCode.success + + elif attribute_state == constants.Parity.space: + self.interface.parity = serial.PARITY_SPACE + return StatusCode.success + + return StatusCode.error_nonsupported_attribute_state + + elif attribute == constants.VI_ATTR_ASRL_RI_STATE: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_RTS_STATE: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_ASRL_STOP_BITS: + bits = self.interface.stopbits + if bits == serial.STOPBITS_ONE: + return constants.StopBits.one + elif bits == serial.STOPBITS_ONE_POINT_FIVE: + return constants.StopBits.one_and_a_half + elif bits == serial.STOPBITS_TWO: + return constants.StopBits.two + + raise Exception('Unknown bits value: %r' % bits) + + elif attribute == constants.VI_ATTR_ASRL_XOFF_CHAR: + raise NotImplementedError + + raise UnknownAttribute(attribute) diff --git a/pyvisa-py/sessions.py b/pyvisa-py/sessions.py new file mode 100644 index 0000000..c389fa4 --- /dev/null +++ b/pyvisa-py/sessions.py @@ -0,0 +1,333 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-py.session + ~~~~~~~~~~~~~~~~~ + + Base Session class. + + + :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 + +import abc +import time + +from pyvisa import logger, constants, attributes, compat, rname + +from . import common + + +class UnknownAttribute(Exception): + + def __init__(self, attribute): + self.attribute = attribute + + def __str__(self): + attr = self.attribute + if isinstance(attr, int): + try: + name = attributes.AttributesByID[attr].visa_name + except KeyError: + name = 'Name not found' + + return 'Unknown attribute %s (%s - %s)' % (attr, hex(attr), name) + + return 'Unknown attribute %s' % attr + + __repr__ = __str__ + + +class Session(compat.with_metaclass(abc.ABCMeta)): + """A base class for Session objects. + + 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_name: The resource name. + :param parsed: the parsed resource name (optional). + If not provided, the resource_name will be parsed. + """ + + @abc.abstractmethod + def _get_attribute(self, attribute): + """Get the value for a given VISA attribute for this 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. + :rtype: (unicode | str | list | int, VISAStatus) + """ + + @abc.abstractmethod + def _set_attribute(self, attribute, attribute_state): + """Set the attribute_state value for a given VISA attribute for this session. + + Use to implement custom logic for attributes. + + :param attribute: Resource attribute for which the state query is made. + :param attribute_state: value. + :return: The return value of the library call. + :rtype: VISAStatus + """ + + @abc.abstractmethod + def close(self): + """Close the session. Use it to do final clean ups. + """ + + #: Maps (Interface Type, Resource Class) to Python class encapsulating that resource. + #: dict[(Interface Type, Resource Class) , Session] + _session_classes = dict() + + #: Session type as (Interface Type, Resource Class) + session_type = None + + @classmethod + def get_low_level_info(cls): + return '' + + @classmethod + def iter_valid_session_classes(cls): + """Yield (Interface Type, Resource Class), Session class pair for + valid sessions classes. + """ + + for key, val in cls._session_classes.items(): + if issubclass(val, Session): + yield key, val + + @classmethod + def iter_session_classes_issues(cls): + """Yield (Interface Type, Resource Class), Issues class pair for + invalid sessions classes (i.e. those with import errors). + """ + for key, val in cls._session_classes.items(): + try: + yield key, getattr(val, 'session_issue') + except AttributeError: + pass + + @classmethod + def get_session_class(cls, interface_type, resource_class): + """Return the session class for a given interface type and resource class. + + :type interface_type: constants.InterfaceType + :type resource_class: str + :return: Session + """ + try: + return cls._session_classes[(interface_type, resource_class)] + except KeyError: + raise ValueError('No class registered for %s, %s' % (interface_type, resource_class)) + + @classmethod + def register(cls, interface_type, resource_class): + """Register a session class for a given interface type and resource class. + + :type interface_type: constants.InterfaceType + :type resource_class: str + """ + 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)) + + python_class.session_type = (interface_type, resource_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. + raising a ValueError if called. + + :type interface_type: constants.InterfaceType + :type resource_class: str + """ + # noinspection PyUnusedLocal + def _internal(*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)) + + cls._session_classes[(interface_type, resource_class)] = _internal + + def __init__(self, resource_manager_session, resource_name, parsed=None): + if isinstance(resource_name, common.MockInterface): + parsed = rname.parse_resource_name(resource_name.resource_name) + parsed['mock'] = resource_name + + elif parsed is None: + parsed = rname.parse_resource_name(resource_name) + + self.parsed = parsed + + #: 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 + #: 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} + self.after_parsing() + + def after_parsing(self): + """Override this method to provide custom initialization code, to be + called after the resourcename is properly parsed + """ + + 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`. + + :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. + :rtype: (unicode | str | list | int, VISAStatus) + """ + + # Check if the attribute value is defined. + try: + attr = attributes.AttributesByID[attribute] + except KeyError: + return 0, constants.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 + + # 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. + if attribute in self.attrs: + return self.attrs[attribute], constants.StatusCode.success + + elif attribute == constants.VI_ATTR_TMO_VALUE: + return self.timeout, constants.StatusCode.success + + # 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 + + def set_attribute(self, attribute, attribute_state): + """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`. + + :param attribute: Resource attribute for which the state query is made. + :param attribute_state: value. + :return: The return value of the library call. + :rtype: VISAStatus + """ + + # Check if the attribute value is defined. + try: + attr = attributes.AttributesByID[attribute] + except KeyError: + return constants.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 + + # Check if writing the attribute is allowed. + if not attr.write: + return constants.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. + 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. + + try: + return self._set_attribute(attribute, attribute_state) + except ValueError: + return constants.StatusCode.error_nonsupported_attribute_state + except NotImplementedError: + e = UnknownAttribute(attribute) + logger.exception(str(e)) + return constants.StatusCode.error_nonsupported_attribute + except UnknownAttribute as e: + logger.exception(str(e)) + return constants.StatusCode.error_nonsupported_attribute + + def _read(self, reader, count, end_indicator_checker, suppress_end_en, + termination_char, termination_char_en, timeout_exception): + """Reads data from device or interface synchronously. + + Corresponds to viRead function of the VISA library. + + :param reader: Function to read a single byte. + :type reader: () -> byte + :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 suppress_end_en: suppress end. + :type suppress_end_en: bool + :param termination_char: Number of bytes to be read. + :param termination_char_en: termination char enabled. + :type termination_char_en: boolean + :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. + + start = time.time() + out = b'' + while True: + try: + current = reader() + except timeout_exception: + return out, constants.StatusCode.error_timeout + + if current: + out += 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 + + 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 + + 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 + + if time.time() - start > timeout: + return out, constants.StatusCode.error_timeout diff --git a/pyvisa-py/tcpip.py b/pyvisa-py/tcpip.py new file mode 100644 index 0000000..a862ef8 --- /dev/null +++ b/pyvisa-py/tcpip.py @@ -0,0 +1,480 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-py.tcpip + ~~~~~~~~~~~~~~~ + + TCPIP Session implementation using Python Standard library. + + + :copyright: 2014 by PyVISA-py Authors, see AUTHORS for more details. + :license: MIT, see LICENSE for more details. +""" + +import random +import socket +import select +import time + +from pyvisa import constants, attributes + +from .sessions import Session, UnknownAttribute +from .protocols import vxi11 +from . import common + + +StatusCode = constants.StatusCode +SUCCESS = StatusCode.success + + +@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 + + link = None + max_recv_size = 1024 + + @staticmethod + def list_resources(): + # TODO: is there a way to get this? + return [] + + def after_parsing(self): + # TODO: board_number not handled + # TODO: lan_device_name not handled + self.interface = vxi11.CoreClient(self.parsed.host_address) + + 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) + + if error: + raise Exception("error creating link: %d" % error) + + self.link = link + self.max_recv_size = min(max_recv_size, 2 ** 30) # 1GB + + for name in 'SEND_END_EN,TERMCHAR,TERMCHAR_EN'.split(','): + attribute = getattr(constants, 'VI_ATTR_' + name) + self.attrs[attribute] = attributes.AttributesByID[attribute].default + + def close(self): + self.interface.destroy_link(self.link) + self.interface.close() + self.link = None + self.interface = None + + def read(self, count): + """Reads data from device or interface synchronously. + + Corresponds to viRead function of the VISA library. + + :param count: Number of bytes to be read. + :return: data read, return value of the library call. + :rtype: bytes, VISAStatus + """ + if count < self.max_recv_size: + chunk_length = count + 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] + + read_data = bytearray() + + end_reason = vxi11.RX_END | vxi11.RX_CHR + + read_fun = self.interface.device_read + + status = SUCCESS + + while reason & end_reason == 0: + error, reason, data = read_fun(self.link, chunk_length, self.timeout, + self.lock_timeout, flags, term_char) + + if error: + return read_data, StatusCode.error_io + + read_data.extend(data) + count -= len(data) + + if count <= 0: + status = StatusCode.success_max_count_read + break + + chunk_length = min(count, chunk_length) + + return bytes(read_data), status + + def write(self, data): + """Writes data to device or interface synchronously. + + Corresponds to viWrite function of the VISA library. + + :param data: data to be written. + :type data: str + :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: + + 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] + + error, size = self.interface.device_write(self.link, self.timeout, + self.lock_timeout, flags, block) + + if error: + return offset, StatusCode.error_io + elif size < len(block): + return offset, StatusCode.error_io + + offset += size + num -= size + + return offset, SUCCESS + except vxi11.Vxi11Error: + return 0, StatusCode.error_timeout + + def _get_attribute(self, attribute): + """Get the value for a given VISA attribute for this 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. + :rtype: (unicode | str | list | int, VISAStatus) + """ + + if attribute == constants.VI_ATTR_TCPIP_ADDR: + return self.host_address, SUCCESS + + elif attribute == constants.VI_ATTR_TCPIP_DEVICE_NAME: + raise NotImplementedError + + elif 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 + + elif attribute == constants.VI_ATTR_TCPIP_PORT: + raise NotImplementedError + + elif attribute == constants.VI_ATTR_SUPPRESS_END_EN: + raise NotImplementedError + + raise UnknownAttribute(attribute) + + def _set_attribute(self, attribute, attribute_state): + """Sets the state of an attribute. + + 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. + :return: return value of the library call. + :rtype: VISAStatus + """ + + raise UnknownAttribute(attribute) + + 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: 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) + + return SUCCESS + + def clear(self): + """Clears a device. + + Corresponds to viClear function of the VISA library. + + :return: return value of the library call. + :rtype: VISAStatus + """ + + flags = 0 + + error = self.interface.device_clear(self.link, flags, self.lock_timeout, self.io_timeout) + + if error: + # TODO: Which status to return + raise Exception("error clearing: %d" % error) + + 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, VISAStatus + """ + 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) + + return stb, SUCCESS + + 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. + :rtype: str, VISAStatus + """ + + # TODO: lock type not implemented + flags = 0 + + error = self.interface.device_lock(self.link, flags, self.lock_timeout) + + if error: + # TODO: Which status to return + raise Exception("error locking: %d" % error) + + def unlock(self): + """Relinquishes a lock for the specified resource. + + Corresponds to viUnlock function of the VISA library. + + :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) + + +@Session.register(constants.InterfaceType.tcpip, 'SOCKET') +class TCPIPSocketSession(Session): + """A TCPIP Session that uses the network standard library to do the low level communication. + """ + + lock_timeout = 1000 + timeout = 1000 + + max_recv_size = 4096 + + # This buffer is used to store the bytes that appeared after termination char + _pending_buffer = b'' + + @staticmethod + def list_resources(): + # TODO: is there a way to get this? + return [] + + def after_parsing(self): + # TODO: board_number not handled + + self.interface = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.interface.setblocking(0) + + 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.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(','): + attribute = getattr(constants, 'VI_ATTR_' + name) + self.attrs[attribute] = attributes.AttributesByID[attribute].default + + def close(self): + self.interface.close() + self.interface = None + + def read(self, count): + """Reads data from device or interface synchronously. + + Corresponds to viRead function of the VISA library. + + :param count: Number of bytes to be read. + :return: data read, return value of the library call. + :rtype: bytes, VISAStatus + """ + if count < self.max_recv_size: + chunk_length = count + 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'' + + 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 + + def write(self, data): + """Writes data to device or interface synchronously. + + Corresponds to viWrite function of the VISA library. + + :param data: data to be written. + :type data: str + :return: Number of bytes actually transferred, return value of the library call. + :rtype: int, VISAStatus + """ + + chunk_size = 4096 + + num = sz = len(data) + + offset = 0 + + while num > 0: + + block = data[offset:min(offset+chunk_size, sz)] + + try: + # use select to wait for write ready + select.select([], [self.interface], []) + size = self.interface.send(block) + except socket.timeout as e: + return offset, StatusCode.error_io + + if size < len(block): + return offset, StatusCode.error_io + + offset += size + num -= size + + return offset, SUCCESS + + def _get_attribute(self, attribute): + """Get the value for a given VISA attribute for this 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. + :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): + """Sets the state of an attribute. + + 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. + :return: return value of the library call. + :rtype: VISAStatus + """ + + raise UnknownAttribute(attribute) diff --git a/pyvisa-py/testsuite/__init__.py b/pyvisa-py/testsuite/__init__.py new file mode 100644 index 0000000..6157c88 --- /dev/null +++ b/pyvisa-py/testsuite/__init__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +from __future__ import division, unicode_literals, print_function, absolute_import + +import os + +from pyvisa.compat import unittest + + +def testsuite(): + """A testsuite that has all the pyvisa tests. + """ + return unittest.TestLoader().discover(os.path.dirname(__file__)) + + +def main(): + """Runs the testsuite as command line application. + """ + try: + unittest.main() + except Exception as e: + print('Error: %s' % e) + + +def run(): + """Run all tests. + + :return: a :class:`unittest.TestResult` object + """ + test_runner = unittest.TextTestRunner() + return test_runner.run(testsuite()) + diff --git a/pyvisa-py/usb.py b/pyvisa-py/usb.py new file mode 100644 index 0000000..154879e --- /dev/null +++ b/pyvisa-py/usb.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +""" + pyvisa-py.usb + ~~~~~~~~~~~~~ + + Serial Session implementation using PyUSB. + + + :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 pyvisa import constants, attributes + +from .sessions import Session, UnknownAttribute + +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) + raise + +try: + _ = usb.core.find() +except ValueError 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 + + 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""" + raise NotImplementedError + + @classmethod + def get_low_level_info(cls): + try: + ver = usb.__version__ + except AttributeError: + ver = 'N/A' + + try: + # noinspection PyProtectedMember + backend = usb.core.find()._ctx.backend.__class__.__module__.split('.')[-1] + except: + backend = 'N/A' + + return 'via PyUSB (%s). Backend: %s' % (ver, backend) + + def read(self, count): + """Reads data from device or interface synchronously. + + Corresponds to viRead function of the VISA library. + + :param count: Number of bytes to be read. + :return: data read, return value of the library call. + :rtype: (bytes, VISAStatus) + """ + + supress_end_en, _ = self.get_attribute(constants.VI_ATTR_SUPPRESS_END_EN) + + if supress_end_en: + raise ValueError('VI_ATTR_SUPPRESS_END_EN == True is currently unsupported by pyvisa-py') + + 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), + count, + lambda current: False, + supress_end_en, + term_char, + term_char_en, + usb.USBError) + + def write(self, data): + """Writes data to device or interface synchronously. + + Corresponds to viWrite function of the VISA library. + + :param data: data to be written. + :type data: bytes + :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) + + count = self.interface.write(data) + + return count, SUCCESS + + def close(self): + self.interface.close() + + def _get_attribute(self, attribute): + """Get the value for a given VISA attribute for this 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. + :rtype: (unicode | str | list | int, VISAStatus) + """ + + raise UnknownAttribute(attribute) + + def _set_attribute(self, attribute, attribute_state): + """Sets the state of an attribute. + + 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. + :return: return value of the library call. + :rtype: VISAStatus + """ + + raise UnknownAttribute(attribute) + +@Session.register(constants.InterfaceType.usb, 'INSTR') +class USBInstrSession(USBSession): + """Base class for drivers that communicate with instruments + via usb port using pyUSB + """ + + @staticmethod + def list_resources(): + out = [] + 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) + try: + intfc = intfc[0].index + except (IndexError, AttributeError): + intfc = 0 + out.append(fmt % dict(board=0, + manufacturer_id=dev.idVendor, + model_code=dev.idProduct, + serial_number=dev.serial_number, + usb_interface_number=intfc)) + return out + + def after_parsing(self): + self.interface = usbtmc.USBTMC(int(self.parsed.manufacturer_id, 0), + int(self.parsed.model_code, 0), + self.parsed.serial_number) + + for name in 'SEND_END_EN,TERMCHAR,TERMCHAR_EN'.split(','): + attribute = getattr(constants, 'VI_ATTR_' + name) + self.attrs[attribute] = attributes.AttributesByID[attribute].default + + +@Session.register(constants.InterfaceType.usb, 'RAW') +class USBRawSession(USBSession): + """Base class for drivers that communicate with usb raw devices + via usb port using pyUSB + """ + + @staticmethod + def list_resources(): + out = [] + fmt = 'USB%(board)s::%(manufacturer_id)s::%(model_code)s::' \ + '%(serial_number)s::%(usb_interface_number)s::RAW' + for dev in usbraw.find_raw_devices(): + intfc = usbutil.find_interfaces(dev, bInterfaceClass=0xFF) + try: + intfc = intfc[0].index + except (IndexError, AttributeError): + intfc = 0 + out.append(fmt % dict(board=0, + manufacturer_id=dev.idVendor, + model_code=dev.idProduct, + serial_number=dev.serial_number, + usb_interface_number=intfc)) + return out + + def after_parsing(self): + self.interface = usbraw.USBRawDevice(int(self.parsed.manufacturer_id, 0), + int(self.parsed.model_code, 0), + self.parsed.serial_number) + + for name in 'SEND_END_EN,TERMCHAR,TERMCHAR_EN'.split(','): + attribute = getattr(constants, 'VI_ATTR_' + name) + self.attrs[attribute] = attributes.AttributesByID[attribute].default diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..9717861 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[check-manifest] +ignore = + .travis.yml + tox.ini diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..411b3d2 --- /dev/null +++ b/setup.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +try: + import sys + reload(sys).setdefaultencoding("UTF-8") +except: + pass + + +try: + from setuptools import setup +except ImportError: + print('Please install or upgrade setuptools or pip to continue') + sys.exit(1) + + +import codecs + + +def read(filename): + return codecs.open(filename, encoding='utf-8').read() + + +long_description = '\n\n'.join([read('README'), + read('AUTHORS'), + read('CHANGES')]) + +__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', + 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', + test_suite='pyvisa-py.testsuite.testsuite', + keywords='Remote VISA GPIB USB serial RS232 measurement acquisition', + license='MIT License', + install_requires=requirements, + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: MIT License', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX :: Linux', + 'Operating System :: MacOS :: MacOS X', + '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', + ], + packages=['pyvisa-py', + 'pyvisa-py.protocols', + 'pyvisa-py.testsuite'], + platforms="Linux, Windows, Mac", + use_2to3=False, + zip_safe=False) -- cgit v1.2.3 From a5084db8a354f67e09251045eef39d6aacc46290 Mon Sep 17 00:00:00 2001 From: Ruben Undheim Date: Sun, 28 Aug 2016 13:03:59 +0200 Subject: Import pyvisa-py_0.2-2.debian.tar.xz [dgit import tarball pyvisa-py 0.2-2 pyvisa-py_0.2-2.debian.tar.xz] --- .git-dpm | 11 +++++++++++ changelog | 39 +++++++++++++++++++++++++++++++++++++++ compat | 1 + control | 45 +++++++++++++++++++++++++++++++++++++++++++++ copyright | 46 ++++++++++++++++++++++++++++++++++++++++++++++ gbp.conf | 2 ++ pydist-overrides | 1 + python-pyvisa-py.docs | 1 + python-pyvisa-py.install | 2 ++ python3-pyvisa-py.docs | 1 + python3-pyvisa-py.install | 2 ++ rules | 24 ++++++++++++++++++++++++ source/format | 1 + watch | 2 ++ 14 files changed, 178 insertions(+) create mode 100644 .git-dpm create mode 100644 changelog create mode 100644 compat create mode 100644 control create mode 100644 copyright create mode 100644 gbp.conf create mode 100644 pydist-overrides create mode 100644 python-pyvisa-py.docs create mode 100644 python-pyvisa-py.install create mode 100644 python3-pyvisa-py.docs create mode 100644 python3-pyvisa-py.install create mode 100755 rules create mode 100644 source/format create mode 100644 watch diff --git a/.git-dpm b/.git-dpm new file mode 100644 index 0000000..1e45e71 --- /dev/null +++ b/.git-dpm @@ -0,0 +1,11 @@ +# see git-dpm(1) from git-dpm package +7f408571bb2c389e361ecc356fa4e3565365986b +7f408571bb2c389e361ecc356fa4e3565365986b +7f408571bb2c389e361ecc356fa4e3565365986b +7f408571bb2c389e361ecc356fa4e3565365986b +pyvisa-py_0.2.orig.tar.gz +58f9118ee60dc08dc1ef8c5fa5c3315df374fcff +48322 +debianTag="debian/%e%v" +patchedTag="patched/%e%v" +upstreamTag="upstream/%e%u" diff --git a/changelog b/changelog new file mode 100644 index 0000000..c9fec6c --- /dev/null +++ b/changelog @@ -0,0 +1,39 @@ +pyvisa-py (0.2-2) unstable; urgency=medium + + [ Ondřej Nový ] + * Fixed VCS URL (https) + + [ Ruben Undheim ] + * debian/control: + - Fix circular dependency by moving python-pyvisa to Recommends + - New standards version 3.9.8 - no changes + + -- Ruben Undheim Sun, 28 Aug 2016 13:03:59 +0200 + +pyvisa-py (0.2-1) unstable; urgency=low + + * New upstream release + - Added versioned dependency on python-pyvisa and python3-pyvisa + (debian/control) + * debian/control + - Added space between Python and 3 in the description + * debian/copyright + - Added year 2015 and new AUTHORS + - Added "Upstream-Contact" field. + * debian/.git-dpm: + - Converted to git-dpm patch management + + -- Ruben Undheim Sun, 13 Sep 2015 21:13:05 +0200 + +pyvisa-py (0.1-2) unstable; urgency=low + + * Uploaded to unstable + * Added debian/pydist-overrides to prevent the dependency on pyvisa + + -- Ruben Undheim Thu, 11 Jun 2015 08:39:44 +0000 + +pyvisa-py (0.1-1) experimental; urgency=low + + * Initial release (Closes: #775764) + + -- Ruben Undheim Wed, 25 Feb 2015 22:30:09 +0100 diff --git a/compat b/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/compat @@ -0,0 +1 @@ +9 diff --git a/control b/control new file mode 100644 index 0000000..4922715 --- /dev/null +++ b/control @@ -0,0 +1,45 @@ +Source: pyvisa-py +Section: python +Priority: optional +Maintainer: Debian Python Modules Team +Uploaders: Ruben Undheim +Build-Depends: debhelper (>= 9), + dh-python, + python-all, + python-setuptools, + python3-all, + python3-setuptools +Standards-Version: 3.9.8 +X-Python-Version: all +X-Python3-Version: >= 3.1 +Vcs-Browser: https://anonscm.debian.org/cgit/python-modules/packages/pyvisa-py.git +Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/pyvisa-py.git +Homepage: https://github.com/hgrecco/pyvisa-py + +Package: python-pyvisa-py +Architecture: all +Depends: ${misc:Depends}, ${python:Depends} +Recommends: python-pyvisa (>= 1.8-1) +Description: Backend that implements a large part of the VISA in pure Python + This library provides Python VISA bindings for GPIB, RS232, and USB + instruments. + . + PyVISA started as wrapper for the NI-VISA library and therefore you need to + install National Instruments VISA library in your system. This works most of + the time, for most people. But NI-VISA is a proprietary library that only works + on certain systems. That is when PyVISA-py jumps in. + +Package: python3-pyvisa-py +Architecture: all +Depends: ${misc:Depends}, ${python3:Depends} +Recommends: python3-pyvisa (>= 1.8-1) +Description: Backend that implements a large part of the VISA in pure Python 3 + This library provides Python VISA bindings for GPIB, RS232, and USB + instruments. + . + PyVISA started as wrapper for the NI-VISA library and therefore you need to + install National Instruments VISA library in your system. This works most of + the time, for most people. But NI-VISA is a proprietary library that only works + on certain systems. That is when PyVISA-py jumps in. + . + This package provides Python 3 bindings for pyvisa-py. diff --git a/copyright b/copyright new file mode 100644 index 0000000..8c2c827 --- /dev/null +++ b/copyright @@ -0,0 +1,46 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: pyvisa-py +Upstream-Contact: Hernan E. Grecco +Source: https://github.com/hgrecco/pyvisa-py + +Files: * +Copyright: 2014-2015 PyVISA-py Authors and contributors + 2014 Lance McCulley + 2014-2015 Hernan E. Grecco + 2014-2015 Tobias Müller + 2015 Alex Forencich + 2015 Alexander Bessman + 2015 Colin Marquardt + 2015 Martin Ritter + 2015 Sebastian Held + 2015 Thomas Kopp <20.kopp@gmail.com> + 2015 Thorsten Liebig +License: MIT +Comment: Hernan E. Grecco is the current upstream and his commits (and copyright + dates) can be checked at https://github.com/hgrecco/pyvisa. Copyright + dates for the other contributors mentioned in the AUTHORS file are also + found in the git log at https://github.com/hgrecco/pyvisa. + +Files: debian/* +Copyright: 2015 Ruben Undheim +License: MIT + + +License: MIT + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/gbp.conf b/gbp.conf new file mode 100644 index 0000000..cec628c --- /dev/null +++ b/gbp.conf @@ -0,0 +1,2 @@ +[DEFAULT] +pristine-tar = True diff --git a/pydist-overrides b/pydist-overrides new file mode 100644 index 0000000..fd0d12b --- /dev/null +++ b/pydist-overrides @@ -0,0 +1 @@ +pyvisa python-pyvisa diff --git a/python-pyvisa-py.docs b/python-pyvisa-py.docs new file mode 100644 index 0000000..e845566 --- /dev/null +++ b/python-pyvisa-py.docs @@ -0,0 +1 @@ +README diff --git a/python-pyvisa-py.install b/python-pyvisa-py.install new file mode 100644 index 0000000..0e608fb --- /dev/null +++ b/python-pyvisa-py.install @@ -0,0 +1,2 @@ +usr/lib/python2*/ +docs/*.rst usr/share/doc/python-pyvisa-py/manual/rst diff --git a/python3-pyvisa-py.docs b/python3-pyvisa-py.docs new file mode 100644 index 0000000..e845566 --- /dev/null +++ b/python3-pyvisa-py.docs @@ -0,0 +1 @@ +README diff --git a/python3-pyvisa-py.install b/python3-pyvisa-py.install new file mode 100644 index 0000000..0c009f2 --- /dev/null +++ b/python3-pyvisa-py.install @@ -0,0 +1,2 @@ +usr/lib/python3*/ +docs/*.rst usr/share/doc/python3-pyvisa-py/manual/rst diff --git a/rules b/rules new file mode 100755 index 0000000..391c8a1 --- /dev/null +++ b/rules @@ -0,0 +1,24 @@ +#!/usr/bin/make -f +#DH_VERBOSE = 1 + +%: + dh $@ --with python2,python3 --buildsystem=python_distutils + +override_dh_auto_clean: + $(RM) -r PyVISA_py.egg-info + $(RM) -r build + dh_auto_clean + +override_dh_auto_build: + dh_auto_build + set -ex; for python in $(shell py3versions -r); do \ + $$python setup.py build; \ + done + +override_dh_auto_install: + dh_auto_install + set -ex; for python in $(shell py3versions -r); do \ + $$python setup.py install --install-layout=deb \ + --root $(CURDIR)/debian/tmp; \ + done; + diff --git a/source/format b/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/watch b/watch new file mode 100644 index 0000000..7830e21 --- /dev/null +++ b/watch @@ -0,0 +1,2 @@ +version=3 +https://github.com/hgrecco/pyvisa-py/releases /hgrecco/pyvisa-py/archive/(\d\S+)\.tar\.(?:bz2|gz|xz) -- cgit v1.2.3