├── .gitignore ├── AUTHORS ├── COPYING ├── MANIFEST.in ├── README ├── README.md ├── doc ├── Makefile ├── appendix.rst ├── conf.py ├── examples.rst ├── index.rst ├── intro.rst ├── make.bat └── usbtmc.rst ├── setup.cfg ├── setup.py ├── usbtmc.rules └── usbtmc ├── __init__.py ├── usbtmc.py └── version.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.kate-swp 3 | *.pyc 4 | 5 | build 6 | dist 7 | *.egg-info 8 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Alex Forencich 2 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2017 Alex Forencich 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS COPYING MANIFEST.in 2 | include doc/*.rst doc/Makefile doc/make.bat -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python USBTMC Readme 2 | 3 | For more information and updates: 4 | http://alexforencich.com/wiki/en/python-usbtmc/start 5 | 6 | GitHub repository: 7 | https://github.com/python-ivi/python-usbtmc 8 | 9 | Google group: 10 | https://groups.google.com/d/forum/python-ivi 11 | 12 | ## Introduction 13 | 14 | Python USBTMC provides a pure Python USBTMC driver for controlling instruments 15 | over USB. 16 | 17 | ## Requirements 18 | 19 | * Python 2 or Python 3 20 | * PyUSB 21 | 22 | ## Installation 23 | 24 | Extract and run 25 | 26 | # python setup.py install 27 | 28 | ## Configuring udev 29 | 30 | If you cannot access your device without running your script as root, then you 31 | may need to create a udev rule to properly set the permissions of the device. 32 | First, connect your device and run lsusb. Find the vendor and product IDs. 33 | Then, create a file /etc/udev/rules.d/usbtmc.rules with the following content: 34 | 35 | # USBTMC instruments 36 | 37 | # Agilent MSO7104 38 | SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="0957", ATTRS{idProduct}=="1755", GROUP="usbtmc", MODE="0660" 39 | 40 | substituting the correct idVendor and idProduct from lsusb. You will also need 41 | to create the usbtmc group and add yourself to it or substitute another group 42 | of your choosing. It seems that udev does not allow 0666 rules, usually 43 | overriding the mode to 0664, so you will need to be a member of the associated 44 | group to use the device. 45 | 46 | If you want to put the kernel usbtmc device in the same group, add the 47 | following content to the usbtmc.rules file as well. This is optional as 48 | Python USBTMC bypasses and disconnects the kernel usbtmc driver and the device 49 | will actually disappear from /dev when Python USBTMC connects. 50 | 51 | # Devices 52 | KERNEL=="usbtmc/*", MODE="0660", GROUP="usbtmc" 53 | KERNEL=="usbtmc[0-9]*", MODE="0660", GROUP="usbtmc" 54 | 55 | ## Windows setup 56 | 57 | To use Python USBTMC in Windows, PyUSB and libusb are required. They can be 58 | downloaded from: 59 | 60 | * http://sourceforge.net/projects/pyusb/ 61 | * http://www.libusb.org/wiki/libusb-win32 62 | 63 | An INF file will also need to be created in order to use devices with libusb. 64 | Without a properly set up INF file, Python USBTMC will not be able to locate 65 | the device. There are instructions on the libusb page for how to generate the 66 | INF file. 67 | 68 | ## Usage examples 69 | 70 | Connecting to Agilent MSO7104A via USBTMC: 71 | 72 | import usbtmc 73 | instr = usbtmc.Instrument(2391, 5973) 74 | print(instr.ask("*IDN?")) 75 | # returns 'AGILENT TECHNOLOGIES,MSO7104A,MY********,06.16.0001' 76 | 77 | When multiple instruments are connected they can be identified by serial number: 78 | 79 | import usbtmc 80 | instr = usbtmc.Instrument(2391, 5973, 'MY********') 81 | print(instr.ask("*IDN?")) 82 | # returns 'AGILENT TECHNOLOGIES,MSO7104A,MY********,06.16.0001' 83 | 84 | It is also possible to connect with VISA resource strings like so: 85 | 86 | import usbtmc 87 | instr = usbtmc.Instrument("USB::0x0957::0x1755::INSTR") 88 | print(instr.ask("*IDN?")) 89 | # returns 'AGILENT TECHNOLOGIES,MSO7104A,MY********,06.16.0001' 90 | 91 | and: 92 | 93 | import usbtmc 94 | instr = usbtmc.Instrument("USB::0x0957::0x1755::MY********::INSTR") 95 | print(instr.ask("*IDN?")) 96 | # returns 'AGILENT TECHNOLOGIES,MSO7104A,MY********,06.16.0001' 97 | 98 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PythonUSBTMC.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PythonUSBTMC.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PythonUSBTMC" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PythonUSBTMC" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /doc/appendix.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Appendix 3 | ========== 4 | 5 | License 6 | ======= 7 | 8 | Copyright (c) 2012-2016 Alex Forencich 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in 18 | all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. 27 | 28 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Python USBTMC documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Aug 2 15:09:00 2013. 6 | # 7 | # This file is execfile()d with the current directory set to its containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys, os 16 | 17 | # If extensions (or modules to document with autodoc) are in another directory, 18 | # add these directories to sys.path here. If the directory is relative to the 19 | # documentation root, use os.path.abspath to make it absolute, like shown here. 20 | #sys.path.insert(0, os.path.abspath('.')) 21 | sys.path.insert(0, os.path.abspath("..")) 22 | 23 | 24 | # -- General configuration ----------------------------------------------------- 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be extensions 30 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 31 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ['_templates'] 35 | 36 | # The suffix of source filenames. 37 | source_suffix = '.rst' 38 | 39 | # The encoding of source files. 40 | #source_encoding = 'utf-8-sig' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | project = 'Python USBTMC' 47 | copyright = '2013, Alex Forencich' 48 | 49 | # The version info for the project you're documenting, acts as replacement for 50 | # |version| and |release|, also used in various other places throughout the 51 | # built documents. 52 | # 53 | # The short X.Y version. 54 | version = '0.1' 55 | # The full version, including alpha/beta/rc tags. 56 | release = '0.1' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | #language = None 61 | 62 | # There are two options for replacing |today|: either, you set today to some 63 | # non-false value, then it is used: 64 | #today = '' 65 | # Else, today_fmt is used as the format for a strftime call. 66 | #today_fmt = '%B %d, %Y' 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | exclude_patterns = ['_build'] 71 | 72 | # The reST default role (used for this markup: `text`) to use for all documents. 73 | #default_role = None 74 | 75 | # If true, '()' will be appended to :func: etc. cross-reference text. 76 | #add_function_parentheses = True 77 | 78 | # If true, the current module name will be prepended to all description 79 | # unit titles (such as .. function::). 80 | #add_module_names = True 81 | 82 | # If true, sectionauthor and moduleauthor directives will be shown in the 83 | # output. They are ignored by default. 84 | #show_authors = False 85 | 86 | # The name of the Pygments (syntax highlighting) style to use. 87 | pygments_style = 'sphinx' 88 | 89 | # A list of ignored prefixes for module index sorting. 90 | #modindex_common_prefix = [] 91 | 92 | 93 | # -- Options for HTML output --------------------------------------------------- 94 | 95 | # The theme to use for HTML and HTML Help pages. See the documentation for 96 | # a list of builtin themes. 97 | html_theme = 'default' 98 | 99 | # Theme options are theme-specific and customize the look and feel of a theme 100 | # further. For a list of options available for each theme, see the 101 | # documentation. 102 | #html_theme_options = {} 103 | 104 | # Add any paths that contain custom themes here, relative to this directory. 105 | #html_theme_path = [] 106 | 107 | # The name for this set of Sphinx documents. If None, it defaults to 108 | # " v documentation". 109 | #html_title = None 110 | 111 | # A shorter title for the navigation bar. Default is the same as html_title. 112 | #html_short_title = None 113 | 114 | # The name of an image file (relative to this directory) to place at the top 115 | # of the sidebar. 116 | #html_logo = None 117 | 118 | # The name of an image file (within the static path) to use as favicon of the 119 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 120 | # pixels large. 121 | #html_favicon = None 122 | 123 | # Add any paths that contain custom static files (such as style sheets) here, 124 | # relative to this directory. They are copied after the builtin static files, 125 | # so a file named "default.css" will overwrite the builtin "default.css". 126 | html_static_path = ['_static'] 127 | 128 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 129 | # using the given strftime format. 130 | #html_last_updated_fmt = '%b %d, %Y' 131 | 132 | # If true, SmartyPants will be used to convert quotes and dashes to 133 | # typographically correct entities. 134 | #html_use_smartypants = True 135 | 136 | # Custom sidebar templates, maps document names to template names. 137 | #html_sidebars = {} 138 | 139 | # Additional templates that should be rendered to pages, maps page names to 140 | # template names. 141 | #html_additional_pages = {} 142 | 143 | # If false, no module index is generated. 144 | #html_domain_indices = True 145 | 146 | # If false, no index is generated. 147 | #html_use_index = True 148 | 149 | # If true, the index is split into individual pages for each letter. 150 | #html_split_index = False 151 | 152 | # If true, links to the reST sources are added to the pages. 153 | #html_show_sourcelink = True 154 | 155 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 156 | #html_show_sphinx = True 157 | 158 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 159 | #html_show_copyright = True 160 | 161 | # If true, an OpenSearch description file will be output, and all pages will 162 | # contain a tag referring to it. The value of this option must be the 163 | # base URL from which the finished HTML is served. 164 | #html_use_opensearch = '' 165 | 166 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 167 | #html_file_suffix = None 168 | 169 | # Output file base name for HTML help builder. 170 | htmlhelp_basename = 'PythonUSBTMCdoc' 171 | 172 | 173 | # -- Options for LaTeX output -------------------------------------------------- 174 | 175 | latex_elements = { 176 | # The paper size ('letterpaper' or 'a4paper'). 177 | #'papersize': 'letterpaper', 178 | 179 | # The font size ('10pt', '11pt' or '12pt'). 180 | #'pointsize': '10pt', 181 | 182 | # Additional stuff for the LaTeX preamble. 183 | #'preamble': '', 184 | } 185 | 186 | # Grouping the document tree into LaTeX files. List of tuples 187 | # (source start file, target name, title, author, documentclass [howto/manual]). 188 | latex_documents = [ 189 | ('index', 'PythonUSBTMC.tex', 'Python USBTMC Documentation', 190 | 'Alex Forencich', 'manual'), 191 | ] 192 | 193 | # The name of an image file (relative to this directory) to place at the top of 194 | # the title page. 195 | #latex_logo = None 196 | 197 | # For "manual" documents, if this is true, then toplevel headings are parts, 198 | # not chapters. 199 | #latex_use_parts = False 200 | 201 | # If true, show page references after internal links. 202 | #latex_show_pagerefs = False 203 | 204 | # If true, show URL addresses after external links. 205 | #latex_show_urls = False 206 | 207 | # Documents to append as an appendix to all manuals. 208 | #latex_appendices = [] 209 | 210 | # If false, no module index is generated. 211 | #latex_domain_indices = True 212 | 213 | 214 | # -- Options for manual page output -------------------------------------------- 215 | 216 | # One entry per manual page. List of tuples 217 | # (source start file, name, description, authors, manual section). 218 | man_pages = [ 219 | ('index', 'pythonusbtmc', 'Python USBTMC Documentation', 220 | ['Alex Forencich'], 1) 221 | ] 222 | 223 | # If true, show URL addresses after external links. 224 | #man_show_urls = False 225 | 226 | 227 | # -- Options for Texinfo output ------------------------------------------------ 228 | 229 | # Grouping the document tree into Texinfo files. List of tuples 230 | # (source start file, target name, title, author, 231 | # dir menu entry, description, category) 232 | texinfo_documents = [ 233 | ('index', 'PythonUSBTMC', 'Python USBTMC Documentation', 234 | 'Alex Forencich', 'PythonUSBTMC', 'One line description of project.', 235 | 'Miscellaneous'), 236 | ] 237 | 238 | # Documents to append as an appendix to all manuals. 239 | #texinfo_appendices = [] 240 | 241 | # If false, no module index is generated. 242 | #texinfo_domain_indices = True 243 | 244 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 245 | #texinfo_show_urls = 'footnote' 246 | -------------------------------------------------------------------------------- /doc/examples.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Python USBTMC Examples 3 | ===================== 4 | 5 | Opening a connection 6 | ==================== 7 | 8 | Connect to an Agilent MSO7104A oscilloscope on IP address 192.168.1.104:: 9 | 10 | >>> import usbtmc 11 | >>> instr = usbtmc.Instrument(2391, 5973) 12 | >>> print(instr.ask("*IDN?")) 13 | 'AGILENT TECHNOLOGIES,MSO7104A,MY********,06.16.0001' 14 | 15 | Configuring connections 16 | ======================= 17 | 18 | Open a connection and set the timeout:: 19 | 20 | >>> import usbtmc 21 | >>> instr = usbtmc.Instrument(2391, 5973) 22 | >>> instr.timeout = 60*1000 23 | >>> print(instr.ask("*TST?")) 24 | '0' 25 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. Python USBTMC documentation master file 2 | 3 | Welcome to Python USBTMC's documentation 4 | ======================================== 5 | 6 | This Python package supports the USBTMC instrument control protocol for 7 | controlling instruments over USB. 8 | 9 | See also: 10 | 11 | - `Python USBTMC home page`_ 12 | - `GitHub repository`_ 13 | 14 | .. _`Python USBTMC home page`: http://alexforencich.com/wiki/en/python-usbtmc/start 15 | .. _`GitHub repository`: https://github.com/python-ivi/python-usbtmc 16 | 17 | Contents: 18 | 19 | .. toctree:: 20 | :maxdepth: 2 21 | 22 | intro 23 | examples 24 | usbtmc 25 | appendix 26 | 27 | Indices and tables 28 | ================== 29 | 30 | * :ref:`genindex` 31 | * :ref:`modindex` 32 | * :ref:`search` 33 | 34 | -------------------------------------------------------------------------------- /doc/intro.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Introduction to Python USBTMC 3 | ============================= 4 | 5 | Overview 6 | ======== 7 | This Python package supports the USBTMC instrument control protocol for 8 | controlling instruments over USB. The implementation is pure Python and 9 | highly portable. 10 | 11 | It is released under the MIT license, see LICENSE_ for more 12 | details. 13 | 14 | Copyright (C) 2012-2013 Alex Forencich 15 | 16 | See also: 17 | 18 | - `Python USBTMC home page`_ 19 | - `GitHub repository`_ 20 | 21 | .. _LICENSE: appendix.html#license 22 | .. _`Python USBTMC home page`: http://alexforencich.com/wiki/en/python-usbtmc/start 23 | .. _`GitHub repository`: https://github.com/alexforencich/python-usbtmc 24 | 25 | 26 | Features 27 | ======== 28 | - Supports Python 2 and Python 3 29 | - Pure Python 30 | - Highly portable 31 | - Communicates with instruments that support the USB Test and Measurement Class 32 | 33 | Requirements 34 | ============ 35 | - Python 2 or Python 3 36 | - PyUSB 37 | 38 | 39 | Installation 40 | ============ 41 | 42 | To install the module for all users on the system, administrator rights (root) 43 | are required. 44 | 45 | From source 46 | ~~~~~~~~~~~ 47 | Download the archive, extract, and run:: 48 | 49 | python setup.py install 50 | 51 | Packages 52 | ~~~~~~~~ 53 | There are also packaged versions for some Linux distributions: 54 | 55 | Arch Linux 56 | Python USBTMC is available under the name "python-usbtmc-git" in the AUR. 57 | 58 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PythonUSBTMC.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PythonUSBTMC.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /doc/usbtmc.rst: -------------------------------------------------------------------------------- 1 | =============================== 2 | :mod:`usbtmc` --- USBTMC driver 3 | =============================== 4 | 5 | .. module:: usbtmc 6 | :synopsis: USBTMC driver 7 | 8 | This module provides a USBTMC instrument driver. 9 | 10 | Functions and Exceptions 11 | ------------------------ 12 | 13 | .. exception:: UsbtmcError 14 | 15 | Exception raised on various occasions; argument is a string describing what 16 | is wrong. 17 | 18 | :class:`Instrument` class 19 | ------------------------- 20 | 21 | .. autoclass:: Instrument 22 | :members: 23 | 24 | 25 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [build_sphinx] 2 | source-dir = doc/ 3 | build-dir = doc/_build 4 | all_files = 1 5 | 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | from __future__ import with_statement 3 | 4 | # http://docs.python.org/distutils/ 5 | # http://packages.python.org/distribute/ 6 | try: 7 | from setuptools import setup 8 | except: 9 | from distutils.core import setup 10 | 11 | import os.path 12 | 13 | version_py = os.path.join(os.path.dirname(__file__), 'usbtmc', 'version.py') 14 | with open(version_py, 'r') as f: 15 | d = dict() 16 | exec(f.read(), d) 17 | version = d['__version__'] 18 | 19 | setup( 20 | name = 'python-usbtmc', 21 | description = 'Python USBTMC driver for controlling instruments over USB', 22 | version = version, 23 | long_description = '''This Python package supports the USBTMC instrument 24 | control protocol for controlling instruments over USB.''', 25 | author = 'Alex Forencich', 26 | author_email = 'alex@alexforencich.com', 27 | url = 'http://alexforencich.com/wiki/en/python-usbtmc/start', 28 | download_url = 'http://github.com/python-ivi/python-usbtmc/tarball/master', 29 | keywords = 'USB USBTMC measurement instrument', 30 | license = 'MIT License', 31 | classifiers=[ 32 | 'Development Status :: 4 - Beta', 33 | 'Environment :: Console', 34 | 'License :: OSI Approved :: MIT License', 35 | 'Natural Language :: English', 36 | 'Operating System :: OS Independent', 37 | 'Intended Audience :: Science/Research', 38 | 'Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator', 39 | 'Topic :: Software Development :: Libraries :: Python Modules', 40 | 'Topic :: System :: Hardware :: Hardware Drivers', 41 | 'Topic :: System :: Networking', 42 | 'Programming Language :: Python :: 2', 43 | 'Programming Language :: Python :: 3' 44 | ], 45 | packages = ['usbtmc'] 46 | ) 47 | 48 | -------------------------------------------------------------------------------- /usbtmc.rules: -------------------------------------------------------------------------------- 1 | # USBTMC instruments 2 | 3 | # Agilent/Keysight MSO7104A 4 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0957|2a8d", ATTRS{idProduct}=="1755", GROUP="usbtmc", MODE="0660" 5 | 6 | # Agilent/Keysight MSO7104B 7 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0957|2a8d", ATTRS{idProduct}=="175d", GROUP="usbtmc", MODE="0660" 8 | 9 | # Agilent/Keysight MSO-X 3014A 10 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0957|2a8d", ATTRS{idProduct}=="17a8", GROUP="usbtmc", MODE="0660" 11 | 12 | # Agilent/Keysight MSO-X 4154A 13 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0957|2a8d", ATTRS{idProduct}=="17bc", GROUP="usbtmc", MODE="0660" 14 | 15 | # Agilent/Keysight DSA-X 96204Q 16 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0957|2a8d", ATTRS{idProduct}=="903c", GROUP="usbtmc", MODE="0660" 17 | 18 | # Agilent/Keysight U2001A 19 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0957|2a8d", ATTRS{idProduct}=="2b18", GROUP="usbtmc", MODE="0660" 20 | 21 | # Agilent/Keysight U2002A 22 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0957|2a8d", ATTRS{idProduct}=="2c18", GROUP="usbtmc", MODE="0660" 23 | 24 | # Agilent/Keysight B2962A 25 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0957|2a8d", ATTRS{idProduct}=="9018", GROUP="usbtmc", MODE="0660" 26 | 27 | # Agilent/Keysight U2701A, U2702A 28 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0957|2a8d", ATTRS{idProduct}=="2818|2918", GROUP="usbtmc", MODE="0660" 29 | 30 | # Agilent/Keysight U2722A 31 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0957|2a8d", ATTRS{idProduct}=="4118|4218", GROUP="usbtmc", MODE="0660" 32 | 33 | # Agilent/Keysight U2723A 34 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0957|2a8d", ATTRS{idProduct}=="4318|4418", GROUP="usbtmc", MODE="0660" 35 | 36 | # Agilent/Keysight 33500B 37 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0957|2a8d", ATTRS{idProduct}=="2807", GROUP="usbtmc", MODE="0660" 38 | 39 | # Agilent/Keysight 34461A 40 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0957|2a8d", ATTRS{idProduct}=="1301", GROUP="usbtmc", MODE="0660" 41 | 42 | # Tektronix MDO4104 43 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="0699", ATTRS{idProduct}=="040c", GROUP="usbtmc", MODE="0660" 44 | 45 | # Rigol MSO1104Z 46 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="1ab1", ATTRS{idProduct}=="04ce", GROUP="usbtmc", MODE="0660" 47 | 48 | # Rigol DS2000 49 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="1ab1", ATTRS{idProduct}=="04b0", GROUP="usbtmc", MODE="0660" 50 | 51 | # Rigol DS4000 52 | SUBSYSTEM=="usb", ACTION=="add", ATTRS{idVendor}=="1ab1", ATTRS{idProduct}=="04b1", GROUP="usbtmc", MODE="0660" 53 | 54 | # Kernel devices 55 | KERNEL=="usbtmc/*", MODE="0660", GROUP="usbtmc" 56 | KERNEL=="usbtmc[0-9]*", MODE="0660", GROUP="usbtmc" 57 | -------------------------------------------------------------------------------- /usbtmc/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Python USBTMC driver 4 | 5 | Copyright (c) 2012-2017 Alex Forencich 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | """ 26 | 27 | __all__ = ["usbtmc"] 28 | 29 | from .version import __version__ 30 | from .usbtmc import Instrument, list_devices, list_resources 31 | -------------------------------------------------------------------------------- /usbtmc/usbtmc.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Python USBTMC driver 4 | 5 | Copyright (c) 2012-2017 Alex Forencich 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | """ 26 | 27 | import usb.core 28 | import usb.util 29 | import struct 30 | import time 31 | import os 32 | import re 33 | import sys 34 | 35 | # constants 36 | USBTMC_bInterfaceClass = 0xFE 37 | USBTMC_bInterfaceSubClass = 3 38 | USBTMC_bInterfaceProtocol = 0 39 | USB488_bInterfaceProtocol = 1 40 | 41 | USBTMC_MSGID_DEV_DEP_MSG_OUT = 1 42 | USBTMC_MSGID_REQUEST_DEV_DEP_MSG_IN = 2 43 | USBTMC_MSGID_DEV_DEP_MSG_IN = 2 44 | USBTMC_MSGID_VENDOR_SPECIFIC_OUT = 126 45 | USBTMC_MSGID_REQUEST_VENDOR_SPECIFIC_IN = 127 46 | USBTMC_MSGID_VENDOR_SPECIFIC_IN = 127 47 | USB488_MSGID_TRIGGER = 128 48 | 49 | USBTMC_STATUS_SUCCESS = 0x01 50 | USBTMC_STATUS_PENDING = 0x02 51 | USBTMC_STATUS_FAILED = 0x80 52 | USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS = 0x81 53 | USBTMC_STATUS_SPLIT_NOT_IN_PROGRESS = 0x82 54 | USBTMC_STATUS_SPLIT_IN_PROGRESS = 0x83 55 | USB488_STATUS_INTERRUPT_IN_BUSY = 0x20 56 | 57 | USBTMC_REQUEST_INITIATE_ABORT_BULK_OUT = 1 58 | USBTMC_REQUEST_CHECK_ABORT_BULK_OUT_STATUS = 2 59 | USBTMC_REQUEST_INITIATE_ABORT_BULK_IN = 3 60 | USBTMC_REQUEST_CHECK_ABORT_BULK_IN_STATUS = 4 61 | USBTMC_REQUEST_INITIATE_CLEAR = 5 62 | USBTMC_REQUEST_CHECK_CLEAR_STATUS = 6 63 | USBTMC_REQUEST_GET_CAPABILITIES = 7 64 | USBTMC_REQUEST_INDICATOR_PULSE = 64 65 | 66 | USB488_READ_STATUS_BYTE = 128 67 | USB488_REN_CONTROL = 160 68 | USB488_GOTO_LOCAL = 161 69 | USB488_LOCAL_LOCKOUT = 162 70 | 71 | USBTMC_HEADER_SIZE = 12 72 | 73 | RIGOL_QUIRK_PIDS = [0x04ce, 0x0588] 74 | 75 | 76 | def parse_visa_resource_string(resource_string): 77 | # valid resource strings: 78 | # USB::1234::5678::INSTR 79 | # USB::1234::5678::SERIAL::INSTR 80 | # USB0::0x1234::0x5678::INSTR 81 | # USB0::0x1234::0x5678::SERIAL::INSTR 82 | m = re.match('^(?P(?PUSB)\d*)(::(?P[^\s:]+))' 83 | '(::(?P[^\s:]+(\[.+\])?))(::(?P[^\s:]+))?' 84 | '(::(?PINSTR))$', resource_string, re.I) 85 | 86 | if m is not None: 87 | return dict( 88 | type=m.group('type').upper(), 89 | prefix=m.group('prefix'), 90 | arg1=m.group('arg1'), 91 | arg2=m.group('arg2'), 92 | arg3=m.group('arg3'), 93 | suffix=m.group('suffix') 94 | ) 95 | 96 | 97 | # Exceptions 98 | class UsbtmcException(Exception): 99 | em = {0: "No error"} 100 | 101 | def __init__(self, err=None, note=None): 102 | self.err = err 103 | self.note = note 104 | self.msg = '' 105 | 106 | if err is None: 107 | self.msg = note 108 | else: 109 | if type(err) is int: 110 | if err in self.em: 111 | self.msg = "%d: %s" % (err, self.em[err]) 112 | else: 113 | self.msg = "%d: Unknown error" % err 114 | else: 115 | self.msg = err 116 | if note is not None: 117 | self.msg = "%s [%s]" % (self.msg, note) 118 | 119 | def __str__(self): 120 | return self.msg 121 | 122 | 123 | def list_devices(): 124 | "List all connected USBTMC devices" 125 | 126 | def is_usbtmc_device(dev): 127 | for cfg in dev: 128 | d = usb.util.find_descriptor(cfg, bInterfaceClass=USBTMC_bInterfaceClass, 129 | bInterfaceSubClass=USBTMC_bInterfaceSubClass) 130 | if d is not None: 131 | return True 132 | 133 | if dev.idVendor == 0x1334: 134 | # Advantest 135 | return True 136 | 137 | if dev.idVendor == 0x0957: 138 | # Agilent 139 | if dev.idProduct in [0x2818, 0x4218, 0x4418]: 140 | # Agilent U27xx modular devices in firmware update mode 141 | # 0x2818 for U2701A/U2702A (firmware update mode on power up) 142 | # 0x4218 for U2722A (firmware update mode on power up) 143 | # 0x4418 for U2723A (firmware update mode on power up) 144 | return True 145 | 146 | return False 147 | 148 | return list(usb.core.find(find_all=True, custom_match=is_usbtmc_device)) 149 | 150 | 151 | def list_resources(): 152 | "List resource strings for all connected USBTMC devices" 153 | 154 | res = [] 155 | 156 | for dev in list_devices(): 157 | idVendor = dev.idVendor 158 | idProduct = dev.idProduct 159 | 160 | # "fix" IDs for devices in firmware update mode 161 | if idVendor == 0x0957 and idProduct == 0x2818: 162 | # Agilent U2701A/U2702A firmware update mode 163 | idProduct = 0x2918 164 | 165 | if idVendor == 0x0957 and idProduct == 0x4218: 166 | # Agilent U2722A firmware update mode 167 | idProduct = 0x4118 168 | 169 | if idVendor == 0x0957 and idProduct == 0x4418: 170 | # Agilent U2723A firmware update mode 171 | idProduct = 0x4318 172 | 173 | # attempt to read serial number 174 | iSerial = None 175 | try: 176 | iSerial = dev.serial_number 177 | except: 178 | pass 179 | 180 | # append formatted resource string to list 181 | if iSerial is None: 182 | res.append("USB::%d::%d::INSTR" % (idVendor, idProduct)) 183 | else: 184 | res.append("USB::%d::%d::%s::INSTR" % (idVendor, idProduct, iSerial)) 185 | 186 | return res 187 | 188 | 189 | def find_device(idVendor=None, idProduct=None, iSerial=None): 190 | "Find USBTMC instrument" 191 | 192 | devs = list_devices() 193 | 194 | if len(devs) == 0: 195 | return None 196 | 197 | for dev in devs: 198 | # match VID and PID 199 | found = dev.idVendor == idVendor and dev.idProduct == idProduct 200 | 201 | if idVendor == 0x0957 and idProduct == 0x2918: 202 | # Agilent U2701A/U2702A firmware update mode 203 | if dev.idVendor == idVendor and dev.idProduct == 0x2818: 204 | found = True 205 | 206 | if idVendor == 0x0957 and idProduct == 0x4118: 207 | # Agilent U2722A firmware update mode 208 | if dev.idVendor == idVendor and dev.idProduct == 0x4218: 209 | found = True 210 | 211 | if idVendor == 0x0957 and idProduct == 0x4318: 212 | # Agilent U2723A firmware update mode 213 | if dev.idVendor == idVendor and dev.idProduct == 0x4418: 214 | found = True 215 | 216 | if not found: 217 | continue 218 | 219 | if iSerial is None: 220 | return dev 221 | else: 222 | s = '' 223 | 224 | # try reading serial number 225 | try: 226 | s = dev.serial_number 227 | except: 228 | pass 229 | 230 | if iSerial == s: 231 | return dev 232 | 233 | return None 234 | 235 | 236 | class Instrument(object): 237 | "USBTMC instrument interface client" 238 | def __init__(self, *args, **kwargs): 239 | "Create new USBTMC instrument object" 240 | self.idVendor = 0 241 | self.idProduct = 0 242 | self.iSerial = None 243 | self.device = None 244 | self.cfg = None 245 | self.iface = None 246 | self.term_char = None 247 | 248 | self.bcdUSBTMC = 0 249 | self.support_pulse = False 250 | self.support_talk_only = False 251 | self.support_listen_only = False 252 | self.support_term_char = False 253 | 254 | self.bcdUSB488 = 0 255 | self.support_USB4882 = False 256 | self.support_remote_local = False 257 | self.support_trigger = False 258 | self.support_scpi = False 259 | self.support_SR = False 260 | self.support_RL = False 261 | self.support_DT = False 262 | 263 | self.max_transfer_size = 1024*1024 264 | 265 | self.timeout = 5.0 266 | 267 | self.bulk_in_ep = None 268 | self.bulk_out_ep = None 269 | self.interrupt_in_ep = None 270 | 271 | self.last_btag = 0 272 | self.last_rstb_btag = 0 273 | 274 | self.connected = False 275 | self.reattach = [] 276 | self.old_cfg = None 277 | 278 | # quirks 279 | self.advantest_quirk = False 280 | self.advantest_locked = False 281 | 282 | self.rigol_quirk = False 283 | self.rigol_quirk_ieee_block = False 284 | 285 | resource = None 286 | 287 | # process arguments 288 | if len(args) == 1: 289 | if type(args[0]) == str: 290 | resource = args[0] 291 | else: 292 | self.device = args[0] 293 | if len(args) >= 2: 294 | self.idVendor = args[0] 295 | self.idProduct = args[1] 296 | if len(args) >= 3: 297 | self.iSerial = args[2] 298 | 299 | for op in kwargs: 300 | val = kwargs[op] 301 | if op == 'idVendor': 302 | self.idVendor = val 303 | elif op == 'idProduct': 304 | self.idProduct = val 305 | elif op == 'iSerial': 306 | self.iSerial = val 307 | elif op == 'device': 308 | self.device = val 309 | elif op == 'dev': 310 | self.device = val 311 | elif op == 'term_char': 312 | self.term_char = val 313 | elif op == 'resource': 314 | resource = val 315 | 316 | if resource is not None: 317 | res = parse_visa_resource_string(resource) 318 | 319 | if res is None: 320 | raise UsbtmcException("Invalid resource string", 'init') 321 | 322 | if res['arg1'] is None and res['arg2'] is None: 323 | raise UsbtmcException("Invalid resource string", 'init') 324 | 325 | self.idVendor = int(res['arg1'], 0) 326 | self.idProduct = int(res['arg2'], 0) 327 | self.iSerial = res['arg3'] 328 | 329 | # find device 330 | if self.device is None: 331 | if self.idVendor is None or self.idProduct is None: 332 | raise UsbtmcException("No device specified", 'init') 333 | else: 334 | self.device = find_device(self.idVendor, self.idProduct, self.iSerial) 335 | if self.device is None: 336 | raise UsbtmcException("Device not found", 'init') 337 | 338 | def __del__(self): 339 | if self.connected: 340 | self.close() 341 | 342 | @property 343 | def timeout(self): 344 | return self._timeout 345 | 346 | @timeout.setter 347 | def timeout(self, val): 348 | self._timeout = val 349 | self._timeout_ms = int(val * 1000) 350 | 351 | def open(self): 352 | if self.connected: 353 | return 354 | 355 | # initialize device 356 | 357 | if self.device.idVendor == 0x0957 and self.device.idProduct in [0x2818, 0x4218, 0x4418]: 358 | # Agilent U27xx modular devices 359 | # U2701A/U2702A, U2722A/U2723A 360 | # These devices require a short initialization sequence, presumably 361 | # to take them out of 'firmware update' mode after confirming 362 | # that the firmware version is correct. This is required once 363 | # on every power-on before the device can be used. 364 | # Note that the device will reset and the product ID will change. 365 | # U2701A/U2702A boot 0x2818, usbtmc 0x2918 366 | # U2722A boot 0x4218, usbtmc 0x4118 367 | # U2723A boot 0x4418, usbtmc 0x4318 368 | 369 | serial = self.device.serial_number 370 | 371 | new_id = 0 372 | 373 | if self.device.idProduct == 0x2818: 374 | # U2701A/U2702A 375 | new_id = 0x2918 376 | self.device.ctrl_transfer(bmRequestType=0xC0, bRequest=0x0C, wValue=0x0000, wIndex=0x047E, data_or_wLength=0x0001) 377 | self.device.ctrl_transfer(bmRequestType=0xC0, bRequest=0x0C, wValue=0x0000, wIndex=0x047D, data_or_wLength=0x0006) 378 | self.device.ctrl_transfer(bmRequestType=0xC0, bRequest=0x0C, wValue=0x0000, wIndex=0x0484, data_or_wLength=0x0005) 379 | self.device.ctrl_transfer(bmRequestType=0xC0, bRequest=0x0C, wValue=0x0000, wIndex=0x0472, data_or_wLength=0x000C) 380 | self.device.ctrl_transfer(bmRequestType=0xC0, bRequest=0x0C, wValue=0x0000, wIndex=0x047A, data_or_wLength=0x0001) 381 | self.device.ctrl_transfer(bmRequestType=0x40, bRequest=0x0C, wValue=0x0000, wIndex=0x0475, data_or_wLength=b'\x00\x00\x01\x01\x00\x00\x08\x01') 382 | 383 | if self.device.idProduct in [0x4218, 0x4418]: 384 | # U2722A/U2723A 385 | if self.device.idProduct == 0x4218: 386 | # U2722A 387 | new_id = 0x4118 388 | elif self.device.idProduct == 0x4418: 389 | # U2723A 390 | new_id = 0x4318 391 | self.device.ctrl_transfer(bmRequestType=0xC0, bRequest=0x0C, wValue=0x0000, wIndex=0x047E, data_or_wLength=0x0001) 392 | self.device.ctrl_transfer(bmRequestType=0xC0, bRequest=0x0C, wValue=0x0000, wIndex=0x047D, data_or_wLength=0x0006) 393 | self.device.ctrl_transfer(bmRequestType=0xC0, bRequest=0x0C, wValue=0x0000, wIndex=0x0487, data_or_wLength=0x0005) 394 | self.device.ctrl_transfer(bmRequestType=0xC0, bRequest=0x0C, wValue=0x0000, wIndex=0x0472, data_or_wLength=0x000C) 395 | self.device.ctrl_transfer(bmRequestType=0xC0, bRequest=0x0C, wValue=0x0000, wIndex=0x047A, data_or_wLength=0x0001) 396 | self.device.ctrl_transfer(bmRequestType=0x40, bRequest=0x0C, wValue=0x0000, wIndex=0x0475, data_or_wLength=b'\x00\x00\x01\x01\x00\x00\x08\x01') 397 | 398 | usb.util.dispose_resources(self.device) 399 | self.device = None 400 | 401 | for i in range(40): 402 | self.device = find_device(0x0957, new_id, serial) 403 | if self.device is not None: 404 | break 405 | time.sleep(0.5) 406 | 407 | if self.device is None: 408 | print("Agilent U27xx modular device initialization failed") 409 | 410 | # find first USBTMC interface 411 | for cfg in self.device: 412 | for iface in cfg: 413 | if (iface.bInterfaceClass == USBTMC_bInterfaceClass and 414 | iface.bInterfaceSubClass == USBTMC_bInterfaceSubClass): 415 | # USBTMC device 416 | self.cfg = cfg 417 | self.iface = iface 418 | break 419 | elif (self.device.idVendor == 0x1334): 420 | # Advantest 421 | self.cfg = cfg 422 | self.iface = iface 423 | break 424 | else: 425 | continue 426 | break 427 | 428 | if self.iface is None: 429 | raise UsbtmcException("Not a USBTMC device", 'init') 430 | 431 | try: 432 | self.old_cfg = self.device.get_active_configuration() 433 | except usb.core.USBError: 434 | # ignore exception if configuration is not set 435 | pass 436 | 437 | if self.old_cfg is not None and self.old_cfg.bConfigurationValue == self.cfg.bConfigurationValue: 438 | # already set to correct configuration 439 | 440 | # release kernel driver on USBTMC interface 441 | self._release_kernel_driver(self.iface.bInterfaceNumber) 442 | else: 443 | # wrong configuration or configuration not set 444 | 445 | # release all kernel drivers 446 | if self.old_cfg is not None: 447 | for iface in self.old_cfg: 448 | self._release_kernel_driver(iface.bInterfaceNumber) 449 | 450 | # set proper configuration 451 | self.device.set_configuration(self.cfg) 452 | 453 | # claim interface 454 | usb.util.claim_interface(self.device, self.iface) 455 | 456 | # don't need to set altsetting - USBTMC devices have 1 altsetting as per the spec 457 | 458 | # find endpoints 459 | for ep in self.iface: 460 | ep_dir = usb.util.endpoint_direction(ep.bEndpointAddress) 461 | ep_type = usb.util.endpoint_type(ep.bmAttributes) 462 | 463 | if (ep_type == usb.util.ENDPOINT_TYPE_BULK): 464 | if (ep_dir == usb.util.ENDPOINT_IN): 465 | self.bulk_in_ep = ep 466 | elif (ep_dir == usb.util.ENDPOINT_OUT): 467 | self.bulk_out_ep = ep 468 | elif (ep_type == usb.util.ENDPOINT_TYPE_INTR): 469 | if (ep_dir == usb.util.ENDPOINT_IN): 470 | self.interrupt_in_ep = ep 471 | 472 | if self.bulk_in_ep is None or self.bulk_out_ep is None: 473 | raise UsbtmcException("Invalid endpoint configuration", 'init') 474 | 475 | # set quirk flags if necessary 476 | if self.device.idVendor == 0x1334: 477 | # Advantest/ADCMT devices have a very odd USBTMC implementation 478 | # which requires max 63 byte reads and never signals EOI on read 479 | self.max_transfer_size = 63 480 | self.advantest_quirk = True 481 | 482 | if self.device.idVendor == 0x1ab1 and self.device.idProduct in RIGOL_QUIRK_PIDS: 483 | self.rigol_quirk = True 484 | 485 | if self.device.idProduct == 0x04ce: 486 | self.rigol_quirk_ieee_block = True 487 | 488 | self.connected = True 489 | 490 | self.clear() 491 | 492 | self.get_capabilities() 493 | 494 | def close(self): 495 | if not self.connected: 496 | return 497 | 498 | usb.util.dispose_resources(self.device) 499 | 500 | try: 501 | # reset configuration 502 | if self.cfg.bConfigurationValue != self.old_cfg.bConfigurationValue: 503 | self.device.set_configuration(self.old_cfg) 504 | 505 | # try to reattach kernel driver 506 | for iface in self.reattach: 507 | try: 508 | self.device.attach_kernel_driver(iface) 509 | except: 510 | pass 511 | except: 512 | pass 513 | 514 | self.reattach = [] 515 | 516 | self.connected = False 517 | 518 | def is_usb488(self): 519 | return self.iface.bInterfaceProtocol == USB488_bInterfaceProtocol 520 | 521 | def get_capabilities(self): 522 | 523 | if not self.connected: 524 | self.open() 525 | 526 | b = self.device.ctrl_transfer( 527 | usb.util.build_request_type(usb.util.CTRL_IN, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_INTERFACE), 528 | USBTMC_REQUEST_GET_CAPABILITIES, 529 | 0x0000, 530 | self.iface.index, 531 | 0x0018, 532 | timeout=self._timeout_ms) 533 | if (b[0] == USBTMC_STATUS_SUCCESS): 534 | self.bcdUSBTMC = (b[3] << 8) + b[2] 535 | self.support_pulse = b[4] & 4 != 0 536 | self.support_talk_only = b[4] & 2 != 0 537 | self.support_listen_only = b[4] & 1 != 0 538 | self.support_term_char = b[5] & 1 != 0 539 | 540 | if self.is_usb488(): 541 | self.bcdUSB488 = (b[13] << 8) + b[12] 542 | self.support_USB4882 = b[4] & 4 != 0 543 | self.support_remote_local = b[4] & 2 != 0 544 | self.support_trigger = b[4] & 1 != 0 545 | self.support_scpi = b[4] & 8 != 0 546 | self.support_SR = b[4] & 4 != 0 547 | self.support_RL = b[4] & 2 != 0 548 | self.support_DT = b[4] & 1 != 0 549 | else: 550 | raise UsbtmcException("Get capabilities failed", 'get_capabilities') 551 | 552 | def pulse(self): 553 | """ 554 | Send a pulse indicator request, this should blink a light 555 | for 500-1000ms and then turn off again. (Only if supported) 556 | """ 557 | if not self.connected: 558 | self.open() 559 | 560 | if self.support_pulse: 561 | b = self.device.ctrl_transfer( 562 | usb.util.build_request_type(usb.util.CTRL_IN, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_INTERFACE), 563 | USBTMC_REQUEST_INDICATOR_PULSE, 564 | 0x0000, 565 | self.iface.index, 566 | 0x0001, 567 | timeout=self._timeout_ms) 568 | if (b[0] != USBTMC_STATUS_SUCCESS): 569 | raise UsbtmcException("Pulse failed", 'pulse') 570 | 571 | # message header management 572 | def pack_bulk_out_header(self, msgid): 573 | self.last_btag = btag = (self.last_btag % 255) + 1 574 | return struct.pack('BBBx', msgid, btag, ~btag & 0xFF) 575 | 576 | def pack_dev_dep_msg_out_header(self, transfer_size, eom = True): 577 | hdr = self.pack_bulk_out_header(USBTMC_MSGID_DEV_DEP_MSG_OUT) 578 | return hdr+struct.pack(" 0: 626 | if num <= self.max_transfer_size: 627 | eom = True 628 | 629 | block = data[offset:offset+self.max_transfer_size] 630 | size = len(block) 631 | 632 | req = self.pack_dev_dep_msg_out_header(size, eom) + block + b'\0'*((4 - (size % 4)) % 4) 633 | self.bulk_out_ep.write(req, timeout=self._timeout_ms) 634 | 635 | offset += size 636 | num -= size 637 | except usb.core.USBError: 638 | exc = sys.exc_info()[1] 639 | if exc.errno == 110: 640 | # timeout, abort transfer 641 | self._abort_bulk_out() 642 | raise 643 | 644 | def read_raw(self, num=-1): 645 | "Read binary data from instrument" 646 | 647 | if not self.connected: 648 | self.open() 649 | 650 | read_len = self.max_transfer_size 651 | if 0 < num < read_len: 652 | read_len = num 653 | 654 | eom = False 655 | 656 | term_char = None 657 | 658 | if self.term_char is not None: 659 | term_char = self.term_char 660 | 661 | read_data = b'' 662 | 663 | try: 664 | while not eom: 665 | if not self.rigol_quirk or read_data == b'': 666 | 667 | # if the rigol sees this again, it will restart the transfer 668 | # so only send it the first time 669 | 670 | req = self.pack_dev_dep_msg_in_header(read_len, term_char) 671 | self.bulk_out_ep.write(req, timeout=self._timeout_ms) 672 | 673 | resp = self.bulk_in_ep.read(read_len+USBTMC_HEADER_SIZE+3, timeout=self._timeout_ms) 674 | 675 | if sys.version_info >= (3, 2): 676 | resp = resp.tobytes() 677 | else: 678 | resp = resp.tostring() 679 | 680 | if self.rigol_quirk and read_data: 681 | pass # do nothing, the packet has no header if it isn't the first 682 | else: 683 | msgid, btag, btaginverse, transfer_size, transfer_attributes, data = self.unpack_dev_dep_resp_header(resp) 684 | 685 | 686 | if self.rigol_quirk: 687 | # rigol devices only send the header in the first packet, and they lie about whether the transaction is complete 688 | if read_data: 689 | read_data += resp 690 | else: 691 | if self.rigol_quirk_ieee_block and data.startswith(b"#"): 692 | 693 | # ieee block incoming, the transfer_size usbtmc header is lying about the transaction size 694 | l = int(chr(data[1])) 695 | n = int(data[2:l+2]) 696 | 697 | transfer_size = n + (l+2) # account for ieee header 698 | 699 | read_data += data 700 | 701 | if len(read_data) >= transfer_size: 702 | read_data = read_data[:transfer_size] # as per usbtmc spec section 3.2 note 2 703 | eom = True 704 | else: 705 | eom = False 706 | else: 707 | eom = transfer_attributes & 1 708 | read_data += data 709 | 710 | # Advantest devices never signal EOI and may only send one read packet 711 | if self.advantest_quirk: 712 | break 713 | 714 | if num > 0: 715 | num = num - len(data) 716 | if num <= 0: 717 | break 718 | if num < read_len: 719 | read_len = num 720 | except usb.core.USBError: 721 | exc = sys.exc_info()[1] 722 | if exc.errno == 110: 723 | # timeout, abort transfer 724 | self._abort_bulk_in() 725 | raise 726 | 727 | return read_data 728 | 729 | def ask_raw(self, data, num=-1): 730 | "Write then read binary data" 731 | # Advantest/ADCMT hardware won't respond to a command unless it's in Local Lockout mode 732 | was_locked = self.advantest_locked 733 | try: 734 | if self.advantest_quirk and not was_locked: 735 | self.lock() 736 | self.write_raw(data) 737 | return self.read_raw(num) 738 | finally: 739 | if self.advantest_quirk and not was_locked: 740 | self.unlock() 741 | 742 | def write(self, message, encoding='utf-8'): 743 | "Write string to instrument" 744 | if type(message) is tuple or type(message) is list: 745 | # recursive call for a list of commands 746 | for message_i in message: 747 | self.write(message_i, encoding) 748 | return 749 | 750 | self.write_raw(str(message).encode(encoding)) 751 | 752 | def read(self, num=-1, encoding='utf-8'): 753 | "Read string from instrument" 754 | return self.read_raw(num).decode(encoding).rstrip('\r\n') 755 | 756 | def ask(self, message, num=-1, encoding='utf-8'): 757 | "Write then read string" 758 | if type(message) is tuple or type(message) is list: 759 | # recursive call for a list of commands 760 | val = list() 761 | for message_i in message: 762 | val.append(self.ask(message_i, num, encoding)) 763 | return val 764 | 765 | # Advantest/ADCMT hardware won't respond to a command unless it's in Local Lockout mode 766 | was_locked = self.advantest_locked 767 | try: 768 | if self.advantest_quirk and not was_locked: 769 | self.lock() 770 | self.write(message, encoding) 771 | return self.read(num, encoding) 772 | finally: 773 | if self.advantest_quirk and not was_locked: 774 | self.unlock() 775 | 776 | def read_stb(self): 777 | "Read status byte" 778 | 779 | if not self.connected: 780 | self.open() 781 | 782 | if self.is_usb488(): 783 | rstb_btag = (self.last_rstb_btag % 128) + 1 784 | if rstb_btag < 2: 785 | rstb_btag = 2 786 | self.last_rstb_btag = rstb_btag 787 | 788 | b = self.device.ctrl_transfer( 789 | bmRequestType=usb.util.build_request_type(usb.util.CTRL_IN, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_INTERFACE), 790 | bRequest=USB488_READ_STATUS_BYTE, 791 | wValue=rstb_btag, 792 | wIndex=self.iface.index, 793 | data_or_wLength=0x0003, 794 | timeout=self._timeout_ms 795 | ) 796 | if (b[0] == USBTMC_STATUS_SUCCESS): 797 | # check btag 798 | if rstb_btag != b[1]: 799 | raise UsbtmcException("Read status byte btag mismatch", 'read_stb') 800 | if self.interrupt_in_ep is None: 801 | # no interrupt channel, value is here 802 | return b[2] 803 | else: 804 | # read response from interrupt channel 805 | resp = self.interrupt_in_ep.read(2, timeout=self._timeout_ms) 806 | if resp[0] != rstb_btag + 128: 807 | raise UsbtmcException("Read status byte btag mismatch", 'read_stb') 808 | else: 809 | return resp[1] 810 | else: 811 | raise UsbtmcException("Read status failed", 'read_stb') 812 | else: 813 | return int(self.ask("*STB?")) 814 | 815 | def trigger(self): 816 | "Send trigger command" 817 | 818 | if not self.connected: 819 | self.open() 820 | 821 | if self.support_trigger: 822 | data = self.pack_usb488_trigger() 823 | print(repr(data)) 824 | self.bulk_out_ep.write(data, timeout=self._timeout_ms) 825 | else: 826 | self.write("*TRG") 827 | 828 | def clear(self): 829 | "Send clear command" 830 | 831 | if not self.connected: 832 | self.open() 833 | 834 | # Send INITIATE_CLEAR 835 | b = self.device.ctrl_transfer( 836 | bmRequestType=usb.util.build_request_type(usb.util.CTRL_IN, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_INTERFACE), 837 | bRequest=USBTMC_REQUEST_INITIATE_CLEAR, 838 | wValue=0x0000, 839 | wIndex=self.iface.index, 840 | data_or_wLength=0x0001, 841 | timeout=self._timeout_ms 842 | ) 843 | if (b[0] == USBTMC_STATUS_SUCCESS): 844 | # Initiate clear succeeded, wait for completion 845 | while True: 846 | # Check status 847 | b = self.device.ctrl_transfer( 848 | bmRequestType=usb.util.build_request_type(usb.util.CTRL_IN, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_INTERFACE), 849 | bRequest=USBTMC_REQUEST_CHECK_CLEAR_STATUS, 850 | wValue=0x0000, 851 | wIndex=self.iface.index, 852 | data_or_wLength=0x0002, 853 | timeout=self._timeout_ms 854 | ) 855 | time.sleep(0.1) 856 | if (b[0] != USBTMC_STATUS_PENDING): 857 | break 858 | # Clear halt condition 859 | self.bulk_out_ep.clear_halt() 860 | else: 861 | raise UsbtmcException("Clear failed", 'clear') 862 | 863 | def _abort_bulk_out(self, btag=None): 864 | "Abort bulk out" 865 | 866 | if not self.connected: 867 | return 868 | 869 | if btag is None: 870 | btag = self.last_btag 871 | 872 | # Send INITIATE_ABORT_BULK_OUT 873 | b = self.device.ctrl_transfer( 874 | bmRequestType=usb.util.build_request_type(usb.util.CTRL_IN, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_ENDPOINT), 875 | bRequest=USBTMC_REQUEST_INITIATE_ABORT_BULK_OUT, 876 | wValue=btag, 877 | wIndex=self.bulk_out_ep.bEndpointAddress, 878 | data_or_wLength=0x0002, 879 | timeout=self._timeout_ms 880 | ) 881 | if (b[0] == USBTMC_STATUS_SUCCESS): 882 | # Initiate abort bulk out succeeded, wait for completion 883 | while True: 884 | # Check status 885 | b = self.device.ctrl_transfer( 886 | bmRequestType=usb.util.build_request_type(usb.util.CTRL_IN, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_ENDPOINT), 887 | bRequest=USBTMC_REQUEST_CHECK_ABORT_BULK_OUT_STATUS, 888 | wValue=0x0000, 889 | wIndex=self.bulk_out_ep.bEndpointAddress, 890 | data_or_wLength=0x0008, 891 | timeout=self._timeout_ms 892 | ) 893 | time.sleep(0.1) 894 | if (b[0] != USBTMC_STATUS_PENDING): 895 | break 896 | else: 897 | # no transfer in progress; nothing to do 898 | pass 899 | 900 | def _abort_bulk_in(self, btag=None): 901 | "Abort bulk in" 902 | 903 | if not self.connected: 904 | return 905 | 906 | if btag is None: 907 | btag = self.last_btag 908 | 909 | # Send INITIATE_ABORT_BULK_IN 910 | b = self.device.ctrl_transfer( 911 | bmRequestType=usb.util.build_request_type(usb.util.CTRL_IN, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_ENDPOINT), 912 | bRequest=USBTMC_REQUEST_INITIATE_ABORT_BULK_IN, 913 | wValue=btag, 914 | wIndex=self.bulk_in_ep.bEndpointAddress, 915 | data_or_wLength=0x0002, 916 | timeout=self._timeout_ms 917 | ) 918 | if (b[0] == USBTMC_STATUS_SUCCESS): 919 | # Initiate abort bulk in succeeded, wait for completion 920 | while True: 921 | # Check status 922 | b = self.device.ctrl_transfer( 923 | bmRequestType=usb.util.build_request_type(usb.util.CTRL_IN, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_ENDPOINT), 924 | bRequest=USBTMC_REQUEST_CHECK_ABORT_BULK_IN_STATUS, 925 | wValue=0x0000, 926 | wIndex=self.bulk_in_ep.bEndpointAddress, 927 | data_or_wLength=0x0008, 928 | timeout=self._timeout_ms 929 | ) 930 | time.sleep(0.1) 931 | if (b[0] != USBTMC_STATUS_PENDING): 932 | break 933 | else: 934 | # no transfer in progress; nothing to do 935 | pass 936 | 937 | def remote(self): 938 | "Send remote command" 939 | raise NotImplementedError() 940 | 941 | def local(self): 942 | "Send local command" 943 | raise NotImplementedError() 944 | 945 | def lock(self): 946 | "Send lock command" 947 | 948 | if not self.connected: 949 | self.open() 950 | 951 | if self.advantest_quirk: 952 | # This Advantest/ADCMT vendor-specific control command enables remote control and must be sent before any commands are exchanged 953 | # (otherwise READ commands will only retrieve the latest measurement) 954 | self.advantest_locked = True 955 | self.device.ctrl_transfer(bmRequestType=0xA1, bRequest=0xA0, wValue=0x0001, wIndex=0x0000, data_or_wLength=1) 956 | else: 957 | raise NotImplementedError() 958 | 959 | def unlock(self): 960 | "Send unlock command" 961 | 962 | if not self.connected: 963 | self.open() 964 | 965 | if self.advantest_quirk: 966 | # This Advantest/ADCMT vendor-specific control command enables remote control and must be sent before any commands are exchanged 967 | # (otherwise READ commands will only retrieve the latest measurement) 968 | self.advantest_locked = False 969 | self.device.ctrl_transfer(bmRequestType=0xA1, bRequest=0xA0, wValue=0x0000, wIndex=0x0000, data_or_wLength=1) 970 | else: 971 | raise NotImplementedError() 972 | 973 | def advantest_read_myid(self): 974 | 975 | if not self.connected: 976 | self.open() 977 | 978 | "Read MyID value from Advantest and ADCMT devices" 979 | if self.advantest_quirk: 980 | # This Advantest/ADCMT vendor-specific control command reads the "MyID" identifier 981 | try: 982 | return int(self.device.ctrl_transfer(bmRequestType=0xC1, bRequest=0xF5, wValue=0x0000, wIndex=0x0000, data_or_wLength=1)[0]) 983 | except: 984 | return None 985 | else: 986 | raise NotImplementedError() 987 | 988 | def _release_kernel_driver(self, interface_number): 989 | if os.name == 'posix': 990 | if self.device.is_kernel_driver_active(interface_number): 991 | self.reattach.append(interface_number) 992 | try: 993 | self.device.detach_kernel_driver(interface_number) 994 | except usb.core.USBError as e: 995 | sys.exit( 996 | "Could not detach kernel driver from interface({0}): {1}".format(interface_number, 997 | str(e))) 998 | -------------------------------------------------------------------------------- /usbtmc/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.8' 2 | --------------------------------------------------------------------------------