pax_global_header00006660000000000000000000000064145723264570014531gustar00rootroot0000000000000052 comment=32ca1d5848e7a1a0a91f4c5ca907c81f7e9b5c6c pyvisa-py-0.7.2/000077500000000000000000000000001457232645700135005ustar00rootroot00000000000000pyvisa-py-0.7.2/.github/000077500000000000000000000000001457232645700150405ustar00rootroot00000000000000pyvisa-py-0.7.2/.github/FUNDING.yml000066400000000000000000000012151457232645700166540ustar00rootroot00000000000000# These are supported funding model platforms github: [MatthieuDartiailh] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] pyvisa-py-0.7.2/.github/ISSUE_TEMPLATE/000077500000000000000000000000001457232645700172235ustar00rootroot00000000000000pyvisa-py-0.7.2/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000006211457232645700217140ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- To Reproduce -------------- Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Output of `pyvisa-info`** pyvisa-py-0.7.2/.github/ISSUE_TEMPLATE/instrument-communication-issue.md000066400000000000000000000016711457232645700257530ustar00rootroot00000000000000--- name: Instrument communication issue about: Template for requesting help communcating with a specific instrument title: "[COM] Communication issue with XXX using XXX" labels: instrument assignees: '' --- Instrument details ------------------ * Model: * Communication: TCPIP, GPIB, .... * Link to the documentation (if available): Output of `pyvisa-info` ----------------------- pyvisa-py-0.7.2/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000025061457232645700206440ustar00rootroot00000000000000 - [ ] Closes # (insert issue number if relevant) - [ ] Executed ``black . && isort -c . && flake8`` with no errors - [ ] The change is fully covered by automated unit tests - [ ] Documented in docs/ as appropriate - [ ] Added an entry to the CHANGES file pyvisa-py-0.7.2/.github/dependabot.yml000066400000000000000000000002421457232645700176660ustar00rootroot00000000000000version: 2 updates: # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly"pyvisa-py-0.7.2/.github/workflows/000077500000000000000000000000001457232645700170755ustar00rootroot00000000000000pyvisa-py-0.7.2/.github/workflows/ci.yml000066400000000000000000000046061457232645700202210ustar00rootroot00000000000000name: Continuous Integration on: schedule: - cron: "0 0 * * 2" push: branches: - main - staging - trying pull_request: branches: - main paths: - .github/workflows/ci.yml - "pyvisa_py/**" - pyproject.toml - setup.py jobs: formatting: name: Check code formatting runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install tools run: | python -m pip install --upgrade pip pip install ruff mypy pytest pip install git+https://github.com/pyvisa/pyvisa.git@main - name: Formatting run: | ruff format pyvisa_py --check; - name: Linting if: always() run: | ruff pyvisa_py; - name: Mypy if: always() run: | mypy pyvisa_py; tests: name: Unit tests runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install git+https://github.com/pyvisa/pyvisa.git#egg=pyvisa - name: Install project run: | pip install -e . - name: Test with pytest run: | pip install pytest-cov pytest pyvisa_py/testsuite --cov pyvisa_py --cov-report xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} flags: unittests name: codecov-umbrella fail_ci_if_error: true # Added to summarize the matrix (otherwise we would need to list every single # job in bors.toml) tests-result: name: Tests result if: always() needs: - tests runs-on: ubuntu-latest steps: - name: Mark the job as a success if: needs.tests.result == 'success' run: exit 0 - name: Mark the job as a failure if: needs.tests.result != 'success' run: exit 1 pyvisa-py-0.7.2/.github/workflows/docs.yml000066400000000000000000000016621457232645700205550ustar00rootroot00000000000000name: Documentation building on: schedule: - cron: "0 0 * * 2" push: branches: - main - staging - trying pull_request: branches: - main paths: - .github/workflows/docs.yml - "pyvisa_py/**" - "docs/**" - setup.py jobs: docs: name: Docs building runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r docs/requirements.txt pip install git+https://github.com/pyvisa/pyvisa.git - name: Install project run: | pip install . - name: Install graphviz uses: ts-graphviz/setup-graphviz@v2 - name: Build documentation run: | mkdir docs_output; sphinx-build docs/source docs_output -W -b html; pyvisa-py-0.7.2/.github/workflows/release.yml000066400000000000000000000063131457232645700212430ustar00rootroot00000000000000name: Build and upload wheels on: workflow_dispatch: schedule: - cron: '0 0 * * 3' push: tags: - '*' jobs: build_sdist: name: Build sdist runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Get history and tags for SCM versioning to work run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Setup Python uses: actions/setup-python@v5 - name: Build sdist run: | pip install --upgrade pip pip install wheel build python -m build . -s - name: Test sdist run: | pip install pytest pip install dist/*.tar.gz python -X dev -m pytest --pyargs pyvisa_py - name: Store artifacts uses: actions/upload-artifact@v4 with: name: artifact-sdist path: dist/*.tar.gz build_wheel: name: Build wheel runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Get history and tags for SCM versioning to work run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Setup Python uses: actions/setup-python@v5 - name: Build wheels run: | pip install --upgrade pip pip install wheel build python -m build . -w - name: Test wheel run: | pip install pytest pip install dist/*.whl python -X dev -m pytest --pyargs pyvisa_py - name: Store artifacts uses: actions/upload-artifact@v4 with: name: artifact-wheel path: dist/*.whl release_upload: name: Create Release and Upload Release Asset runs-on: ubuntu-latest if: github.event_name == 'push' needs: [build_wheel, build_sdist] steps: - name: Create Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} release_name: Release ${{ github.ref }} draft: false prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'a') || contains(github.ref, 'b')}} - uses: actions/download-artifact@v4 with: pattern: articfact-* path: dist merge-multiple: true - name: Upload Release Asset id: upload-release-asset uses: shogo82148/actions-upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: dist/* upload_pypi: if: github.event_name == 'push' needs: [build_wheel, build_sdist] runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 with: pattern: artifact-* path: dist merge-multiple: true - uses: pypa/gh-action-pypi-publish@master with: user: __token__ password: ${{ secrets.pypi_password }} # To test: # repository_url: https://test.pypi.org/legacy/ pyvisa-py-0.7.2/.gitignore000066400000000000000000000003721457232645700154720ustar00rootroot00000000000000*~ __pycache__ *egg-info* *.pyc .DS_Store docs/_build/ .idea build/ dist/ MANIFEST .tox .eggs # WebDAV file system cache files .DAV/ _test/ .spyproject/ .mypy_cache/ .pytest_cache/ .cache/ .vscode .dmypy.json # auto-generated version file version.pypyvisa-py-0.7.2/.pre-commit-config.yaml000066400000000000000000000005611457232645700177630ustar00rootroot00000000000000repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. rev: v0.3.0 hooks: # Run the linter. - id: ruff # Run the formatter. - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: '' # Use the sha / tag you want to point at hooks: - id: mypy additional_dependencies: [numpy, typing_extensions]pyvisa-py-0.7.2/.readthedocs.yaml000066400000000000000000000011141457232645700167240ustar00rootroot00000000000000# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-20.04 tools: python: "3.9" # Build documentation in the docs/source directory with Sphinx sphinx: configuration: docs/source/conf.py # Enable epub output formats: - epub # Optionally declare the Python requirements required to build your docs python: install: - requirements: docs/requirements.txt - method: pip path: . pyvisa-py-0.7.2/AUTHORS000066400000000000000000000012411457232645700145460ustar00rootroot00000000000000pyvisa-py was started by Hernan E. Grecco . It is now maintained by Matthieu C. Dartiailh Other contributors, listed alphabetically, are: * Alex Forencich * Alexander Bessman * Colin Marquardt * Lance McCulley * Martin Ritter * Matthieu C. Dartiailh * 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) pyvisa-py-0.7.2/CHANGES000066400000000000000000000172171457232645700145030ustar00rootroot00000000000000PyVISA-py Changelog =================== 0.7.2 (07/03/2024) ------------------ - fix usbtmc to use MaxPacketSize reported by endpoint PR #417 0.7.1 (26/10/2023) ------------------ - add URL-support to ASLR devices PR #386 - add support for GPIB secondary addresses - fix missing sock.close() in rpc _connect() - Adjusted how `iter_bytes` works to be more accurate to the VISA spec and removed it from the `serial` module (it can still be found in `common`) - fix HiSLIP message tracking after read timeout PR #376 - handle read_termination of null in tcipip PR #394 - fix tcpip keepalive PR #396 - store more attributes for USB resources PR #399 0.7.0 (05/05/2023) ------------------ - add support for the flush operation with TCPIP::SOCKET resources PR #350 - drop support for Python 3.7 PR #362 - fix listing of available resources PR #362 - fix hislip support for custom sub_addresses PR #359 - fix bad USBRaw resource preventing enumeration of other resources PR #370 0.6.3 (17-02-2023) ------------------ - fix bad behavior on PyVISA 1.12 and hence on Python 3.7 PR #357 0.6.x is the last version that will support Python 3.7 0.6.2 (08-02-2023) ------------------ - fix usb resource handling by avoiding multiple calls to set_configuration PR #352 - formatting fixes on files using "black" PR #352 0.6.1 (25-01-2023) ------------------ - fix listing resources when some optional dependencies are missing PR #349 - properly list discovered TCPIP resources PR #349 - fix pyvisa-info output for TCPIP and GPIB resources PR #349 0.6.0 (22-12-2022) ------------------ - fix writing large messages over TCPIP using the VXI-11 protocol PR #343 - add support for the hislip protocol over TCPIP PR #331 - allow to list TCPIP resources PR #326 In order to discover resources over all subnets psutil needs to be installed - attempt to stabilize access to USBTMC resources PR #335 Reduce the number of device reset performed and only set all settings if it is meaningful (more than one settings exist.) A huge thanks to @bobmacnamara for his work adding hislip and vicp support ! 0.5.3 (12-05-2022) ------------------ - fix tcp/ip connections dropping from inside Docker containers after 5 minute idling #285 - fix ControlFlow.none as an invalid attribute in serial.py PR #317 - VXI11 bug fix: skip over stale rx packets instead of raising an exception. PR #322 - VXI11 bug fix: to ensure all data gets sent, replace calls to sock.send() with calls to sock.sendall(), and replace calls to sock.sendto() with calls to a routine that loops until all data is sent. PR #322 0.5.2 (04-02-2020) ------------------ - handle SUPPRESS_END_EN in usb.py to fix #293 PR #294 - add python_requires to avoid people trying to get a new pyvisa-py on Python 2 PR #295 This addresses pyvisa issue #578 0.5.1 (30-09-2020) ------------------ - list serial resources under Windows without the COM prefix #269 - fix writing to serial resources PR #277 - fix return value of USB close method PR #265 - fix pyvisa version constraint PR #268 0.5.0 (16-09-2020) ------------------ In 0.5.0, the Python package installed in site-packages in now called pyvisa_py which makes it a valid python package. This change was decided because the old name was causing issues with tools such as Mypy and PyInstaller. - start running testsuite on Keysight buildbot PR #247 #252 - fix import in gpib.py when using linux-gpib PR #246 - fix opening some TCPIP resources PR #242 Some instrument do not indicate that the returned packet is the last one, so fix the number of returned packet for a TCPIP INSTR resource. - add typing validation through mypy PR #238 - use Numpy style dosctrings PR #238 - format the code with black and isort PR #236 - drop Python 2 support and run against PyVISA 1.11 PR #222 - usbtmc: improve support for USB488 devices. PR #241 For instrument that support REN_CONTROL, we now always assert the REN line. - fix a crash on Windows when opening multiple USBTMC devices 0.4.1 (2020-05-27) ------------------ - fix typo in tcpip.py PR #233 (back-ported) 0.4.0 (2020-05-06) ------------------ - support device lan name in TCPIP INSTR resources PR #226 - fix handling of VXI11 flags in device_write PR #226 - do not enforce 4 bytes padding RFC 1014 since some instrument do not respect it (Issue # 225) PR #226 - fix not setting stop bits with serial devices PR #205 - fix improper wait time before a timeout in the TCPIP backend PR # 173 - add GPIB support for proprietary device drivers on Windows and Linux (experimental): try importing gpib-ctypes if linux-gpib is not present. fix #105 #137 - fix return types of PyVisaLibrary and Session methods to match pyvisa.highlevel.VisaLibraryBase fix #169 PR #170 - avoid double closing of gpib resources PR #171 - fix initialization of timeout for the USB resources (the default was set before creating the underlying connection to which the timeout must be passed and was not). PR #167 - implement USBTMC abort sequence after read timeout to avoid losing communication with the instrument after a timeout occurs. PR #179 - fix custom timeout for USB instruments. PR #179 - fix triggering for all protocols. PR #180 - add support for "quirky" devices made by Rigol. PR #186 PR #207 - add support for Visa flush operation. PR #208 - fix reading large amounts of data from some instruments when using VXI-11. PR #209 0.3.1 (2018-09-12) ------------------ - Fix initialization of timeout (issue identified in TCPIP resources) PR #160 0.3 (2018-09-05) ---------------- - Fix handling of seesion registration under Python 3.7 PR #155 - Add read_stb, assert_trigger, lock, unlock to highlevel PR #139 - Fix timeout handling in usb PR #144 - Add gpib_command and assert_trigger to GPIB PR # 136 - Handle ValueError in usb list fix #131 PR #132 - Fix reading on GPIB and implement clear and gpib_send_ifc PR #132 - Do not error when listing USB devices PR #126 - Fix an error in the handling of the termchar for TCPIP INSTR PR #126 - Make list_resources return an empty tuple instead of erroring PR #121 - Proper support for timeout in TCPIP INSTR sessions PR #120 #127 #130 #144 - Proper encoding of data before transfer for all backends PR #119 - Unify use of StatusCode PR #118 - Improve handling of sessions attrs PR #116 - TCPIP SOCKET timeout handling improvement PR #115 - Fix compatibility with pyserial 3.0 PR #112 - TCPIP SOCKET handler read should not block PR #107 - TCPIP error handling fixes PR #100 - Use repr() instead of str() to log RPC record PR #97 - Speed up large transfer over GPIB 2beb52a5bcea2dae32d4a9908dc19f7874bfc0b7 - Catch GPIB errors while enumerating devices 9fea9d5c40cc6c33ce1244c209e5e576a33abfc2 - Add a serial poll function to GPIB backend PR #67 - Handle timeout in USB TMC backend PR #64 - Make USB TMC backend faster by transferring multiple bytes PR #63 - Fix issue with encoding before data transfer PR #59 # - Get Linux GPIB version PR #55 - Fix broken import in TCPIP sessions PR #51 0.2 (2015-08-25) ---------------- - 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 pyvisa-py-0.7.2/LICENSE000066400000000000000000000021241457232645700145040ustar00rootroot00000000000000The 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. pyvisa-py-0.7.2/MANIFEST.in000066400000000000000000000002451457232645700152370ustar00rootroot00000000000000include README AUTHORS CHANGES LICENSE recursive-include pyvisa-py * recursive-include docs * prune docs/build global-exclude *.pyc *~ .DS_Store *__pycache__* *.pyo pyvisa-py-0.7.2/README.rst000066400000000000000000000063631457232645700151770ustar00rootroot00000000000000PyVISA-py ========= .. image:: https://github.com/pyvisa/pyvisa-py/workflows/Continuous%20Integration/badge.svg :target: https://github.com/pyvisa/pyvisa-py/actions :alt: Continuous integration .. image:: https://github.com/pyvisa/pyvisa-py/workflows/Documentation%20building/badge.svg :target: https://github.com/pyvisa/pyvisa-py/actions :alt: Documentation building .. image:: https://dev.azure.com/pyvisa/pyvisa-py/_apis/build/status/pyvisa.pyvisa-py.keysight-assisted?branchName=main :target: https://dev.azure.com/pyvisa/pyvisa-py/_build :alt: Keysight assisted testing .. image:: https://codecov.io/gh/pyvisa/pyvisa-py/branch/main/graph/badge.svg :target: https://codecov.io/gh/pyvisa/pyvisa-py :alt: Code Coverage .. image:: https://readthedocs.org/projects/pyvisa-py/badge/?version=latest :target: https://pyvisa.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://img.shields.io/pypi/l/PyVISA-py :target: https://pypi.python.org/pypi/pyvisa-py :alt: PyPI - License .. image:: https://img.shields.io/pypi/v/PyVISA-py :target: https://pypi.python.org/pypi/pyvisa-py :alt: PyPI 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 IVI-VISA library and therefore you need to install a VISA library in your system (National Instruments, Keysight, etc). This works most of the time, for most people. But IVI-VISA implementations are proprietary libraries 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 3.6+) - PyVISA 1.11+ Optionally: - PySerial (to interface with Serial instruments) - PyUSB (to interface with USB instruments) - linux-gpib (to interface with gpib instruments, only on linux) - gpib-ctypes (to interface with GPIB instruments on Windows and Linux) - psutil (to discover TCPIP devices across multiple interfaces) - zeroconf (for HiSLIP and VICP devices discovery) - pyvicp (to enable the Teledyne LeCroy proprietary VICP protocol) Installation -------------- Using pip: $ pip install pyvisa-py Documentation -------------- The documentation can be read online at https://pyvisa-py.readthedocs.org pyvisa-py-0.7.2/azure-pipelines.yml000066400000000000000000000037641457232645700173510ustar00rootroot00000000000000# Python package # Create and test a Python package on multiple Python versions. # Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more: # https://docs.microsoft.com/azure/devops/pipelines/languages/python trigger: branches: include: - main - staging - trying pr: - main variables: PYVISA_KEYSIGHT_VIRTUAL_INSTR: 1 pool: name: Keysight-based demands: KEYSIGHT -equals TCPIP steps: - script: | export PATH="$HOME/miniconda3/bin:$PATH" echo Create environment conda create -n test_ python=3.9 numpy --yes displayName: "Create environment" - script: | export PATH="$HOME/miniconda3/bin:$PATH" source $HOME/miniconda3/bin/activate echo Activate environment call conda activate test_ echo Install project and required dependencies pip install git+https://github.com/pyvisa/pyvisa.git#egg=pyvisa pip install -e .[serial] displayName: "Install dependencies" - script: | export PATH="$HOME/miniconda3/bin:$PATH" source $HOME/miniconda3/bin/activate echo Activate environment call conda activate test_ echo Install pytest and co pip install pytest pytest-azurepipelines pytest-cov echo Run pytest python -X dev -m pytest --pyargs pyvisa_py --cov pyvisa_py --cov-report xml -v displayName: "Run tests" - script: | export PATH="$HOME/miniconda3/bin:$PATH" source $HOME/miniconda3/bin/activate echo Activate environment call conda activate test_ echo Install codecov pip install codecov echo Run codecov codecov --file coverage.xml --token $(CODECOV_TOKEN) --env PYVISA_KEYSIGHT_VIRTUAL_INSTR --tries 5 --required -F unittest --name codecov-umbrella displayName: "Upload test coverage results" - script: | export PATH="$HOME/miniconda3/bin:$PATH" conda remove -n test_ --all --yes displayName: "Remove test environment" condition: always() pyvisa-py-0.7.2/bors.toml000066400000000000000000000002311457232645700153360ustar00rootroot00000000000000status = ["Check code formatting", "Tests result", "Docs building", "pyvisa.pyvisa-py.keysight-assisted"] delete-merged-branches = true timeout_sec = 300pyvisa-py-0.7.2/dev-requirements.txt000066400000000000000000000000341457232645700175350ustar00rootroot00000000000000ruff sphinx sphinx-rtd-themepyvisa-py-0.7.2/docs/000077500000000000000000000000001457232645700144305ustar00rootroot00000000000000pyvisa-py-0.7.2/docs/Makefile000066400000000000000000000127001457232645700160700ustar00rootroot00000000000000# 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) source # 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.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyvisa.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" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyvisa" @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." pyvisa-py-0.7.2/docs/index.rst000066400000000000000000000076301457232645700162770ustar00rootroot00000000000000: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 pyvisa >>> rm = pyvisa.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. PySerial_ version 3.0 or newer is required. For **TCPIP** we use the :py:mod:`socket` module in the Python Standard Library. On Linux, **GPIB** resources are supported using the `linux-gpib`_ project's Python bindings. On Windows as well as Linux systems with proprietary GPIB device drivers, experimental GPIB support is available through `gpib-ctypes`_. The `gpib-ctypes`_ library is still in development so please report any issues you may encounter. If I only need **TCPIP**, do I need to install PySerial, PyUSB, linux-gpib, or gpib-ctypes? ------------------------------------------------------------------------------------------- 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:: pyvisa-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 - USB RAW - TCPIP SOCKET - GPIB INSTR 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: https://pythonhosted.org/pyserial/ .. _PyVISA: http://pyvisa.readthedocs.org/ .. _PyUSB: https://github.com/pyusb/pyusb .. _PyPI: https://pypi.python.org/pypi/PyVISA-py .. _GitHub: https://github.com/pyvisa/pyvisa-py .. _`National Instruments's VISA`: http://ni.com/visa/ .. _`LibreVISA`: http://www.librevisa.org/ .. _`issue tracker`: https://github.com/pyvisa/pyvisa-py/issues .. _`linux-gpib`: http://linux-gpib.sourceforge.net/ .. _`gpib-ctypes`: https://pypi.org/project/gpib-ctypes/ pyvisa-py-0.7.2/docs/make.bat000066400000000000000000000122521457232645700160370ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 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.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyvisa.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 pyvisa-py-0.7.2/docs/requirements.txt000066400000000000000000000000361457232645700177130ustar00rootroot00000000000000sphinx>=4 sphinx-rtd-theme>=1 pyvisa-py-0.7.2/docs/source/000077500000000000000000000000001457232645700157305ustar00rootroot00000000000000pyvisa-py-0.7.2/docs/source/_static/000077500000000000000000000000001457232645700173565ustar00rootroot00000000000000pyvisa-py-0.7.2/docs/source/_static/logo-full.jpg000066400000000000000000000252221457232645700217630ustar00rootroot00000000000000JFIFHH@ICC_PROFILE0appl mntrRGB XYZ   acspAPPLappl-appl dscmdescogXYZlwtptrXYZbXYZrTRCcprt8chad,gTRCbTRCmluc enUS&~esES&daDK.deDE,fiFI(frFU(*itIT(VnlNL(nbNO&ptBR&svSE&jaJPRkoKR@zhTWlzhCNruRU"plPL,Yleinen RGB-profiiliGenerisk RGB-profilProfil Gnrique RVBN, RGB 000000u( RGB r_icϏPerfil RGB GenricoAllgemeines RGB-Profilfn RGB cϏeNGenerel RGB-beskrivelseAlgemeen RGB-profiel| RGB \ |Profilo RGB GenericoGeneric RGB Profile1I89 ?@>D8;L RGBUniwersalny profil RGBdescGeneric RGB ProfileGeneric RGB ProfileXYZ Zus4XYZ RXYZ tM=XYZ (6curvtextCopyright 2007 Apple Inc., all rights reserved.sf32 B&lExifMM*bj(1r2iHHAdobe Photoshop CS4 Macintosh2013:08:26 20:57:27C     C  " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?(((((((((((((((((?>0 vr_M.p5?/ShFE0<ٺ;7 #Ě'KM!I?P1_",G;In=:lw{e%$qƬd+?ioSKI ^iyyY_$XnkOyw eZns&}hE()8}gu|}x3,\lI>|H֗Jϊ[Wc,Y2Dk*|UVnRi䉃# ??\_`n\{>7sjo#Xy<c+g'@uI%ƻi4}떅ʬB_G)kM'B=~IT:(:(((((((((Ց_iz52X[b}4jH4{^^X[ZZ4~ /$0x~1|d_?/=F~q^#H ؠ*Af㠯nT䜏ˎ>ʊjֹ֓3eN+i{O~(|h}Yn-|w$D2Å8<%Ə/\xIrNɯ[U9:ౖ\GʼFRjT{4i|1SASѵ,1"δ1>ܼ8g܏TmtN}w~ֿqyY_8⠻g~A~x3[Wڋlv+6`ihcBt*:r:(` ( ( ( ( ( ( ( ( ֿa?zK?8?} _:~K?/KXV]aqNr}n _ SџfI֐<#wַ<mO? &^&_ Bff:U%&좯#Zw9%{v,6U#" ks4g"8vq?}t1Ox#?%cmk" KUS{8.vp: q]Sx[E p0ݲ1.#Sd }Z\ͫ~yg؊׳}vg??t߮8)S7Ka|\.+ >[މy:sq+'O*v;Gg&s5NY3qg3ʶ??vzX :Odz ' 'I5{t'Ҿ|SjWY_A!ʟcJu%y?6 j9EEPEPEPEPEPEPEPEP\{?x3Y5a-P}pt9EhJ->oE a)T0yiwD1>?K -{7W>ǶG h"[|%xb2{7|wc/ x Eu kwl4E'a}3Tk>25ә3.c7T%B>lgǐG9Qu[ͫk'Kt9+Yrݖ5EhZ*K[: 9ϯJcxPKǚö==է`9om*.V3@˃Аq>|-O <;gnw 1sƺ<kHUcBk\neB*O[H+q 7 S2oqඞ?.u~\5_Z01{Ud򬾖 ?.c*b3?QEEPEPEPEPEPEPEPEPEPUg]{֭Td ٬3(KiK?ķs F<5ϼ7=kWBҵ0j=j˿vpZQ&xS%&,Q4 s2 c澁6EyaxRIG2JC$( }~'0xzή-F]#l]xs6cTWލo.⋿?\k&%s"8ݜk1"!|gϦjljUo)^87Tf lY,k9\g5dwu;|~55jlhFsW!n]VҠ>M=tѱEӻ'VR\sY';L麄:^=FLyX $g߲o߈?q:]˧77S4JCIbcbNH5' qIŻ[&m cqJ?MZ+h=ew%-tb}$t 33^vU8&zJU%W=Ҋ}|o#m-3OU..aK3ʇQq99f_g,-t#nS"Oq! G @$9՞SJ:XjՔRVC6W7wQ_fo|ocj4JIDDۈs=_7>4|6GiGOE9 8\mĪ'9>kk<_6qE|/2~>:-\u_ _^heiW3 "HPgI8'%H-BI_1ң[Nu>Z=Z|w$wXgv fcyşv-/y<.|Ckʴ_-|E?h1RO \ޘ4=CNIl:>ˢ> un0KkLHh }~} .^:$*Wk? N&rX*ivO#|Iً6P]:;PoHxC. /Nf&`IP | x BQ~V嘼Mhk (ۃψ+x'FHԵ *KY2LQ!hI*ge1у}{fY0XyVGW.o[ Ũ\#U2 ;Q0XQ]{[7VL(;¼5& 5&0^?0`8:]_şw߈]Vk5)C(T?*i x+Km?I-migTE`5\SI4(u$GinUm="#Egeq֭3­gJ7e՟gxo-uAeitKyL$*2#,p{şG>6x3ִwhxգu J4M?H<6~>a!|^4T+cpΗ~͟xuׁ[MM^0a O?1_q ,NX^|}MBs_x4Jw^y>rp/ᦛvqV"Kg_ ߱gtɡh O+ \0;BG5 "EE,q"F0M_7*V[ɷAN;E$|;|ɟxk+$>/WIeG&}LĿ9\%}rsO>'5=LVmJװ*&WPDWmE|u9MK!i5C:̰[ϪXr@iiap2;p>~W_/*xD~wW uYntI,DDO8_=6|u ?|W&]n!qyLs$97͢N=UTuG?V[ž1|oxG\״giJwV⺚IQ>Ҕ] ^V 4UyntkG'OVl΄c{If̂+ 2CfI7WW4qpd\VP'+?_{ _/?EWb>&ة4U>&ة4_'2y@?6?'R{3zӥ0x#I++N8kolRr蝗|]?=_YxŖ$A[+'̒BW4Q_wl} C OlE[^e֭vF庈mg_ß$Sn+ç륧_ɽN]@~6~~~~V6۶e\gKq;dIA1 }/ Ԏ*\>s ^>vվhcgu*8e5{Za?~W^4f⛟2" H2T#xTkMō֖y2o& 9%>'6ZMm>;[XD5R,+zN旧E}fKXAr׫V*?og^- ^ŬoGuuhs/nU/c_d7QжnH@y9cNOfu=>"}cR+u8|i!d\t7 -$%\z Q>6WQF[KVn}9 +lt, j6L-\cH''6s<^U3ӂ;d#a8,w-=74|Sy7(SE&}LĿ9\%rK?`o3qUI>?jt)ş`:h0|<ަmW2u%5> ' G ? a%~/l]&Ǟiy20 A=i;KhNլ%o#捿V ] <-(1UnX{Cq޾l`1w<Dou)SX{Q6ݟ)as*(Jm??o? o(}xy`*I aXe*yh֚o_5[u-J6 2GT+3Q/i3oXX81 ,D潿uSQbj9{&8*0}ҷ~ؚW~-cĚVZho@~5:xPg\Wd㬾ro(>'*8tjzml̖KwD]@$ kqEH)?7d:9j6$p]`#ºDw]>I[i^6 )rXPI~_A9=-Qss{Y߽#ُw4w|YstFu_3DAmml23}3úekA.5GI 8Qyncө:9JإqIhZ-G )6o]Oƈ>$ֿ5Z\WnP1\0aw&ހ9qei:agaN.e%u'C_tW."TiYgSib?<<|fu<]CwGѮ$I5 WYusGo @#v(9+ំOVD" $GZh+4kcdcV=<&[r~h~Oث⏇ 77pI\%>21I+d|W}V9i[YTi[M|-m{__foz핮xᗆtۨu(.c- )Ul_8_UeylWRN=4NgSPڳ24{ Z5I#{2g/UN$d8$sZQ^cbQE ( e= (3, 8): from importlib.metadata import version as get_version else: from importlib_metadata import version as get_version # 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-Py" 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 = get_version("pyvisa_py") 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/3", None)} pyvisa-py-0.7.2/docs/source/faq.rst000066400000000000000000000107131457232645700172330ustar00rootroot00000000000000.. _faq: FAQ === 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 IVI compliant VISA implementations available (`National Instruments NI-VISA`_ , `Keysight IO Libraries`_, `Tektronix TekVISA`_, etc) are proprietary libraries that only work on certain systems. We wanted to provide a compatible alternative. Are GBIP secondary addresses supported? --------------------------------------- GPIB secondary addresses are supported in NI-VISA fashion, meaning that the secondary address is not 96 to 126 as transmitted on the bus, but 0 to 30. For expample, `GPIB0::9::1::INSTR` is the address of the first VXI module controlled by a GPIB VXI command module set to primary address `9`, while the command module itself is found at `GPIB0::9::0::INSTR`, which is distinct from a pure primary address like `GPIB0::9::INSTR`. ``ResourceManager.list_resources()`` has become slower as a result, as it now needs to check 992 addresses per GPIB controller instead of just 31. For every primary address where no listener is detected, all secondary addresses are checked for listeners as well to find, for example, VXI modules controlled by an HP E1406A. For primary addresses where a listener is detected, no secondary addresses are checked as most devices simply ignore secondary addressing. If you have a device that reacts to the primary address and has different functionality on some secondary addresses, please leave a bug report. Can PyVISA-py be used from a VM? -------------------------------- Because PyVISA-py access hardware resources such as USB ports, running from a VM can cause issues like unexpected timeouts because the VM does not receive the response. You should consult your VM manual to determine if you are able to setup the VM in such a way that it works. See https://github.com/pyvisa/pyvisa-py/issues/243 for the kind of issue it can cause. Can PyVISA-py be used from a Docker container? ---------------------------------------------- As the Windows variant of Docker can forward neither USB ports nor GPIB interfaces, the obvious choice would be to connect via TCP/IP. The problem of a Docker container is that idle connections are disconnected by the VPN garbage collection. For this reason it is reasonable to enable keepalive packets. The VISA attribute `VI_ATTR_TCPIP_KEEPALIVE` has been modified to work for all TCP/IP instruments. Enabling this option can be done with: inst.set_visa_attribute(pyvisa.constants.ResourceAttribute.tcpip_keepalive, True) where `inst` is an active TCP/IP visa session. (see https://tech.xing.com/a-reason-for-unexplained-connection-timeouts-on-kubernetes-docker-abd041cf7e02 if you want to read more about connection dropping in docker containers) Why not using LibreVISA? ------------------------ LibreVISA_ is still young and appears mostly unmaintained at this point (latest release is from 2013). However, you can already use it with the IVI backend as it has the same API. We think that PyVISA-py is easier to hack and we can quickly reach feature parity with other IVI-VISA implementation 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: https://pythonhosted.org/pyserial/ .. _PyVISA: http://pyvisa.readthedocs.org/ .. _PyUSB: https://github.com/pyusb/pyusb .. _PyPI: https://pypi.python.org/pypi/PyVISA-py .. _GitHub: https://github.com/pyvisa/pyvisa-py .. _`National Instruments NI-VISA`: http://ni.com/visa/ .. _`LibreVISA`: http://www.librevisa.org/ .. _`issue tracker`: https://github.com/pyvisa/pyvisa-py/issues .. _`linux-gpib`: http://linux-gpib.sourceforge.net/ .. _`gpib-ctypes`: https://pypi.org/project/gpib-ctypes/ .. _`Tektronix TekVISA`: https://www.tek.com/en/support/software/driver/tekvisa-connectivity-software-v420 .. _`Keysight IO Libraries`: https://www.keysight.com/us/en/lib/software-detail/computer-software/io-libraries-suite-downloads-2175637.html pyvisa-py-0.7.2/docs/source/index.rst000066400000000000000000000031331457232645700175710ustar00rootroot00000000000000: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 pyvisa >>> rm = pyvisa.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. Currently Pyvisa-py support the following resources: - TCPIP INSTR - TCPIP SOCKET - GPIB INSTR - ASRL INSTR - USB INSTR - USB RAW Note: ASRL INSTR supports also URL Handlers like - loop:// --> ASLRloop://::INSTR - socket:// --> ASRLsocket://::INSTR These entries will not be listed during the device discovery `rm.list_resources()`. For further details see https://pyserial.readthedocs.io/en/latest/url_handlers.html You can report a problem or ask for features in the `issue tracker`_. Or get the code in GitHub_. .. toctree:: :maxdepth: 2 Installation FAQ .. _PyVISA: http://pyvisa.readthedocs.org/ .. _GitHub: https://github.com/pyvisa/pyvisa-py .. _`issue tracker`: https://github.com/pyvisa/pyvisa-py/issues pyvisa-py-0.7.2/docs/source/installation.rst000066400000000000000000000102101457232645700211550ustar00rootroot00000000000000.. _installation: Installation ============ Pyvisa-py is available on PyPI_ and can be easily installed using pip: pip install pyvisa-py Pyvisa-py runs on Python 3.6+. If you do not install any extra library pyvisa-py will only be able to access tcpip resources. The following sections will describe what extra libraries you need to install and how to configure them to use other resources. Ethernet resources: TCPIP INSTR/SOCKET -------------------------------------- Pyvisa-py relies on :py:mod:`socket` module in the Python Standard Library to interact with the instrument which you do not need to install any extra library to access those resources. To discover VXI-11 devices on all network interfaces, please install `psutil`_. Otherwise, discovery will only occur on the default network interface. Discovery of both HiSLIP and VICP devices relies on `mDNS`_, which is a protocol for service discovery in a local area network. To enable resource discovery for HiSLIP and VICP, you should install `zeroconf`_. The TCP/IP VICP protocol (proprietary to Teledyne LeCroy) depends on the `pyvicp`_ package. You should install this package if you need to use VICP. Serial resources: ASRL INSTR ---------------------------- To access serial resources, you should install PySerial_. Version 3.0 or newer is required. No special configuration is required. GPIB resources: GPIB INSTR -------------------------- On all platforms, using **GPIB** resources requires to install a gpib driver. On Windows, it is install as part of NI-VISA or Keysight VISA for example. On MacOSX, you should install the NI-488 library from National instrument. On Linux, you can use a commercial driver (NI) or the `linux-gpib`_ project. On Linux, `linux-gpib`_ comes with Python bindings so you do not have to install any extra library. On all systems with GPIB device drivers, GPIB support is available through `gpib-ctypes`_. You should not have to perform any special configuration after the install. USB resources: USB INSTR/RAW ---------------------------- For **USB** resources, you need to install PyUSB_. PyUSB_ relies on USB driver library such as libusb 0.1, libusb 1.0, libusbx, libusb-win32 and OpenUSB that you should also install. Please refer to PyUSB_ documentation for more details. On Unix system, one may have to modify udev rules to allow non-root access to the device you are trying to connect to. The following tutorial describes how to do it http://ask.xmodulo.com/change-usb-device-permission-linux.html. On Windows, you may have to uninstall the USBTMC-specific driver installed by Windows and re-install a generic driver. Please check `libusb's guide`_ for more details, but installing a ``WinUSB`` driver with Zadig_ should be a good start. Note that on Windows, devices that are already open cannot be detected and will not be returned by ``ResourceManager.list_resources``. How do I know if PyVISA-py is properly installed? ------------------------------------------------- Using the pyvisa information tool. Run in your console:: pyvisa-info You will get info about PyVISA, the installed backends and their options. Using the development version ----------------------------- You can install the latest development version (at your own risk) directly form GitHub_:: $ pip install -U git+https://github.com/pyvisa/pyvisa-py.git .. _PySerial: https://pythonhosted.org/pyserial/ .. _PyVISA: http://pyvisa.readthedocs.org/ .. _PyUSB: https://github.com/pyusb/pyusb .. _PyPI: https://pypi.python.org/pypi/PyVISA-py .. _GitHub: https://github.com/pyvisa/pyvisa-py .. _`National Instruments's VISA`: http://ni.com/visa/ .. _`LibreVISA`: http://www.librevisa.org/ .. _`issue tracker`: https://github.com/pyvisa/pyvisa-py/issues .. _`linux-gpib`: http://linux-gpib.sourceforge.net/ .. _`gpib-ctypes`: https://pypi.org/project/gpib-ctypes/ .. _`psutil`: https://pypi.org/project/psutil/ .. _`mDNS`: https://en.wikipedia.org/wiki/Multicast_DNS .. _`zeroconf`: https://pypi.org/project/zeroconf/ .. _`pyvicp`: https://pypi.org/project/pyvicp/ .. _`libusb's guide`: https://github.com/libusb/libusb/wiki/Windows#user-content-How_to_use_libusb_on_Windows .. _`Zadig`: https://zadig.akeo.ie/ pyvisa-py-0.7.2/pyproject.toml000066400000000000000000000072051457232645700164200ustar00rootroot00000000000000[project] name = "PyVISA-py" description = "Pure Python implementation of a VISA library." readme = "README.rst" requires-python = ">=3.8" license = { file = "LICENSE" } authors = [{ name = "Hernan E. Grecco", email = "hernan.grecco@gmail.com" }] maintainers = [{ name = "Matthieu C. Dartiailh", email = "m.dartiailh@gmail.com" }] keywords = ["VISA", "GPIB", "USB", "serial", "RS232", "measurement", "acquisition"] 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 :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] dependencies = ["pyvisa>=1.13.0", "typing_extensions"] dynamic = ["version"] [project.optional-dependencies] gpib-ctypes = ["gpib-ctypes>=0.3.0"] serial = ["pyserial>=3.0"] usb = ["pyusb"] psutil = ["psutil"] hislip-discovery = ["zeroconf"] vicp = ["pyvicp", "zeroconf"] [project.urls] homepage = "https://github.com/pyvisa/pyvisa-py" documentation = "https://pyvisa-py.readthedocs.io/en/latest/" repository = "https://github.com/pyvisa/pyvisa-py" changelog = "https://github.com/pyvisa/pyvisa-py/blob/main/CHANGES" [build-system] requires = ["setuptools>=61.2", "wheel", "setuptools_scm[toml]>=3.4.3"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] write_to = "pyvisa_py/version.py" write_to_template = """ # This file is auto-generated by setuptools-scm do NOT edit it. from collections import namedtuple #: A namedtuple of the version info for the current release. _version_info = namedtuple("_version_info", "major minor micro status") parts = "{version}".split(".", 3) version_info = _version_info( int(parts[0]), int(parts[1]), int(parts[2]), parts[3] if len(parts) == 4 else "", ) # Remove everything but the 'version_info' from this module. del namedtuple, _version_info, parts __version__ = "{version}" """ [tool.ruff] src = ["src"] extend-exclude = ["pyvisa/thirdparty/*"] line-length = 88 [tool.ruff.lint] select = ["C", "E", "F", "W", "I", "C90", "RUF"] extend-ignore = ["E501", "RUF012"] [tool.ruff.lint.isort] combine-as-imports = true known-first-party = ["pyvisa"] [tool.ruff.lint.mccabe] max-complexity = 20 [tool.pytest.ini_options] minversion = "6.0" [tool.mypy] follow_imports = "normal" strict_optional = true [[tool.mypy.overrides]] module = ["usb.*", "serial.*", "gpib.*", "Gpib.*", "gpib_ctypes.*", "pyvicp.*"] ignore_missing_imports = true [tool.coverage] [tool.coverage.run] branch = true source = ["pyvisa_py"] [tool.coverage.report] # Regexes for lines to exclude from consideration exclude_lines = [ # Have to re-enable the standard pragma "pragma: no cover", # Don't complain if tests don't hit defensive assertion code: "raise NotImplementedError", "pass", # Don't complain about abstract methods, they aren't run: "@(abc\\.)?abstractmethod", # Don't complain about type checking "if TYPE_CHECKING:", # Don't complain about ellipsis in overload "\\.\\.\\.", ] pyvisa-py-0.7.2/pyvisa_py/000077500000000000000000000000001457232645700155235ustar00rootroot00000000000000pyvisa-py-0.7.2/pyvisa_py/__init__.py000066400000000000000000000012671457232645700176420ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Pure Python backend for PyVISA. :copyright: 2014-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ import sys if sys.version_info >= (3, 8): from importlib.metadata import PackageNotFoundError, version else: from importlib_metadata import PackageNotFoundError, version # type: ignore __version__ = "unknown" try: __version__ = version(__name__) except PackageNotFoundError: # package is not installed pass # noqa: we need to import so that __init_subclass__() is executed once from . import attributes # noqa: F401 from .highlevel import PyVisaLibrary WRAPPER_CLASS = PyVisaLibrary pyvisa-py-0.7.2/pyvisa_py/attributes.py000066400000000000000000000020531457232645700202630ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Additional Attributes for specific use with the pyvisa-py package. For additional information and VISA attributes see pyvisa.constants :copyright: 2014-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ from pyvisa import constants from pyvisa.attributes import AttrVI_ATTR_TCPIP_KEEPALIVE as former_keepalive class AttrVI_ATTR_TCPIP_KEEPALIVE(former_keepalive): """Requests that a TCP/IP provider enable the use of keep-alive packets. Altering the standard PyVISA attribute to also work on INSTR sessions as they are using sockets in pyvisa-py as well. After the system detects that a connection was dropped, VISA returns a lost connection error code on subsequent I/O calls on the session. The time required for the system to detect that the connection was dropped is dependent on the system and is not settable. """ resources = [ (constants.InterfaceType.tcpip, "SOCKET"), (constants.InterfaceType.tcpip, "INSTR"), ] pyvisa-py-0.7.2/pyvisa_py/common.py000066400000000000000000000073031457232645700173700ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Common code. :copyright: 2014-2020 by PyVISA-sim Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ import logging from typing import Iterator, Optional from pyvisa import logger logger = logging.LoggerAdapter(logger, {"backend": "py"}) # type: ignore class NamedObject(object): """A class to construct named sentinels.""" #: Name used to identify the sentinel name: str def __init__(self, name) -> None: self.name = name def __repr__(self) -> str: return "<%s>" % self.name __str__ = __repr__ def int_to_byte(val): return val.to_bytes(1, "big") # TODO(anyone): This is copypasta from `pyvisa-sim` project - find a way to # reduce duplication, probably in that project instead of here. def _create_bitmask(bits: int) -> int: """Create a bitmask for the given number of bits.""" mask = (1 << bits) - 1 return mask # TODO(anyone): This is copypasta from `pyvisa-sim` project - find a way to # reduce duplication, probably in that project instead of here. def iter_bytes( data: bytes, data_bits: Optional[int] = None, send_end: Optional[bool] = None ) -> Iterator[bytes]: """Clip values to the correct number of bits per byte. Serial communication may use from 5 to 8 bits. Parameters ---------- data : The data to clip as a byte string. data_bits : How many bits per byte should be sent. Clip to this many bits. For example: data_bits=5: 0xff (0b1111_1111) --> 0x1f (0b0001_1111). Acceptable range is 5 to 8, inclusive. Values above 8 will be clipped to 8. This maps to the VISA attribute VI_ATTR_ASRL_DATA_BITS. send_end : If None (the default), apply the mask that is determined by data_bits. If False, apply the mask and set the highest (post-mask) bit to 0 for all bytes. If True, apply the mask and set the highest (post-mask) bit to 0 for all bytes except for the final byte, which has the highest bit set to 1. References ---------- + https://www.ivifoundation.org/downloads/Architecture%20Specifications/vpp43_2022-05-19.pdf, + https://www.ni.com/docs/en-US/bundle/ni-visa/page/ni-visa/vi_attr_asrl_data_bits.html, + https://www.ni.com/docs/en-US/bundle/ni-visa/page/ni-visa/vi_attr_asrl_end_out.html """ if send_end and data_bits is None: raise ValueError("'send_end' requires a valid 'data_bits' value.") if data_bits is None: for d in data: yield bytes([d]) else: if data_bits <= 0: raise ValueError("'data_bits' cannot be zero or negative") if data_bits > 8: data_bits = 8 if send_end is None: # only apply the mask mask = _create_bitmask(data_bits) for d in data: yield bytes([d & mask]) elif bool(send_end) is False: # apply the mask and set highest bits to 0 # This is effectively the same has reducing the mask by 1 bit. mask = _create_bitmask(data_bits - 1) for d in data: yield bytes([d & mask]) elif bool(send_end) is True: # apply the mask and set highest bits to 0 # This is effectively the same has reducing the mask by 1 bit. mask = _create_bitmask(data_bits - 1) for d in data[:-1]: yield bytes([d & mask]) # except for the last byte which has it's highest bit set to 1. last_byte = data[-1] highest_bit = 1 << (data_bits - 1) yield bytes([(last_byte & mask) | highest_bit]) else: raise ValueError(f"Unknown 'send_end' value '{send_end}'") pyvisa-py-0.7.2/pyvisa_py/gpib.py000066400000000000000000001011051457232645700170140ustar00rootroot00000000000000# -*- coding: utf-8 -*- """GPIB Session implementation using linux-gpib or gpib-ctypes. :copyright: 2015-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ import ctypes # Used for missing bindings not ideal from bisect import bisect from typing import Any, Iterator, List, Tuple, Union from pyvisa import attributes, constants, logger from pyvisa.constants import ResourceAttribute, StatusCode from pyvisa.rname import GPIBInstr, GPIBIntfc from .sessions import Session, UnknownAttribute try: GPIB_CTYPES = True from gpib_ctypes import gpib # typing: ignore from gpib_ctypes.Gpib import Gpib # typing: ignore from gpib_ctypes.gpib.gpib import _lib as gpib_lib # typing: ignore try: # Add some extra binding not available by default extra_funcs = [ ("ibcac", [ctypes.c_int, ctypes.c_int], ctypes.c_int), ("ibgts", [ctypes.c_int, ctypes.c_int], ctypes.c_int), ("ibpct", [ctypes.c_int], ctypes.c_int), ] for name, argtypes, restype in extra_funcs: libfunction = gpib_lib[name] libfunction.argtypes = argtypes libfunction.restype = restype except TypeError: msg = ( "gpib_ctypes is installed but could not locate the gpib library.\n" "Please manually load it using:\n" " gpib_ctypes.gpib.gpib._load_lib(filename)\n" "before importing pyvisa." ) Session.register_unavailable(constants.InterfaceType.gpib, "INSTR", msg) Session.register_unavailable(constants.InterfaceType.gpib, "INTFC", msg) raise except ImportError: GPIB_CTYPES = False try: import gpib # typing: ignore from Gpib import Gpib # typing: ignore except ImportError as e: msg = ( "Please install linux-gpib (Linux) or gpib-ctypes (Windows, Linux) " "to use this resource type. Note that installing gpib-ctypes will " "give you access to a broader range of functionalities.\n%s" % e ) Session.register_unavailable(constants.InterfaceType.gpib, "INSTR", msg) Session.register_unavailable(constants.InterfaceType.gpib, "INTFC", msg) raise # patch Gpib to avoid double closing of handles def _patch_Gpib() -> None: if not hasattr(Gpib, "close"): _old_del = Gpib.__del__ def _inner(self): _old_del(self) self._own = False Gpib.__del__ = _inner Gpib.close = _inner _patch_Gpib() def _find_boards() -> Iterator[Tuple[int, int]]: """Find GPIB board addresses.""" for board in range(16): try: yield board, gpib.ask(board, 1) except gpib.GpibError as e: logger.debug("GPIB board %i error in _find_boards(): %s", board, repr(e)) def _find_listeners() -> Iterator[Tuple[int, int, int]]: """Find GPIB listeners.""" for board, boardpad in _find_boards(): for i in range(31): j = 0 try: if boardpad != i and gpib.listener(board, i): yield board, i, j elif boardpad != i: for j in range(96, 126): if gpib.listener(board, i, j): yield board, i, j except gpib.GpibError as e: logger.debug( "GPIB board %i paddr %i saddr %i error in _find_listeners(): %s", board, i, j, repr(e), ) def _analyse_lines_value(value: int, line: int): """Determine the state of a GPIB line based on the iblines byte. Parameters ---------- value : int Value returned by iblines. line : int One of constants.VI_ATTR_GPIB_***_STATE where the *** can be REN, ATN, NDAC, or SRQ Returns ------- constants.LineState State of the line StatusCode Library StatusCode for the operation. """ if line == constants.VI_ATTR_GPIB_REN_STATE: # REN bit valid = 0x10, REN bit value = 0x100 validity_mask = 0x10 value_mask = 0x100 elif line == constants.VI_ATTR_GPIB_ATN_STATE: # ATN bit valid = 0x40, ATN bit value = 0x4000 validity_mask = 0x40 value_mask = 0x4000 elif line == constants.VI_ATTR_GPIB_NDAC_STATE: # NDAC bit valid = 0x2, NDAC bit value = 0x200 validity_mask = 0x2 value_mask = 0x200 elif line == constants.VI_ATTR_GPIB_SRQ_STATE: # SRQ bit valid = 0x20, SRQ bit value = 0x2000 validity_mask = 0x20 value_mask = 0x2000 if not value & validity_mask: return constants.LineState.unknown, StatusCode.success else: if value & value_mask: return constants.LineState.asserted, StatusCode.success else: return constants.LineState.unasserted, StatusCode.success # linux-gpib timeout constants, in seconds. See GPIBSession._set_timeout. TIMETABLE = ( 0, 10e-6, 30e-6, 100e-6, 300e-6, 1e-3, 3e-3, 10e-3, 30e-3, 100e-3, 300e-3, 1.0, 3.0, 10.0, 30.0, 100.0, 300.0, 1000.0, ) def convert_gpib_error( error: gpib.GpibError, status: int, operation: str ) -> StatusCode: """Convert a GPIB error to a VISA StatusCode. Parameters ---------- error : gpib.GpibError Error to use to determine the proper status code. status : int Status byte of the GPIB library. operation : str Name of the operation that caused an exception. Used in logging. Returns ------- StatusCode Status code matching the GPIB error. """ # First check the imeout condition in the status byte if status & 0x4000: return StatusCode.error_timeout # All other cases are hard errors. # In particular linux-gpib simply gives a string we could parse but that # feels brittle. As a consequence we only try to be smart when using # gpib-ctypes. However in both cases we log the exception at debug level. else: logger.debug("Failed to %s.", operation, exc_info=error) if not GPIB_CTYPES: return StatusCode.error_system_error if error.code == 1: return StatusCode.error_not_cic elif error.code == 2: return StatusCode.error_no_listeners elif error.code == 4: return StatusCode.error_invalid_mode elif error.code == 11: return StatusCode.error_nonsupported_operation elif error.code == 1: return StatusCode.error_not_cic elif error.code == 21: return StatusCode.error_resource_locked else: return StatusCode.error_system_error def convert_gpib_status(status: int) -> StatusCode: if status & 0x4000: return StatusCode.error_timeout elif status & 0x8000: return StatusCode.error_system_error else: return StatusCode.success class _GPIBCommon(Session): """Common base class for GPIB sessions. Both INSTR and INTFC resources share the following attributes: - VI_ATTR_INTF_TYPE - VI_ATTR_TMO_VALUE - VI_ATTR_INTF_INST_NAME - VI_ATTR_INTF_NUM - VI_ATTR_DMA_ALLOW_EN - VI_ATTR_SEND_END_EN - VI_ATTR_TERMCHAR - VI_ATTR_TERM_CHAR_EN - VI_ATTR_RD_BUF_OPER_MODE - VI_ATTR_WR_BUF_OPER_MODE - VI_ATTR_FILE_APPEND_EN - VI_ATTR_GPIB_PRIMARY_ADDR - VI_ATTR_GPIB_SECONDARY_ADDR - VI_ATTR_GPIB_REN_STATE """ # Override parsed to take into account the fact that this class is only used # for a specific kind of resource parsed: Union[GPIBIntfc, GPIBInstr] #: Bus wide controller. controller: Gpib @classmethod def get_low_level_info(cls) -> str: try: ver = gpib.version() except AttributeError: ver = "< 4.0" return "via Linux GPIB (%s)" % ver def after_parsing(self) -> None: minor = int(self.parsed.board) # Secondary address (SAD) values should be in the range 96 to 126, # 0 means the SAD is disabled. sad = 0 timeout = 13 send_eoi = 1 eos_mode = 0 self.interface = None if isinstance(self.parsed, GPIBInstr): pad = int(self.parsed.primary_address) if self.parsed.secondary_address is not None: sad = int(self.parsed.secondary_address) + 0x60 # Used to talk to a specific resource self.interface = Gpib( name=minor, pad=pad, sad=sad, timeout=timeout, send_eoi=send_eoi, eos_mode=eos_mode, ) # Bus wide operation self.controller = Gpib(name=minor) # Force timeout setting to interface self.set_attribute( constants.ResourceAttribute.timeout_value, attributes.AttributesByID[constants.VI_ATTR_TMO_VALUE].default, ) for name in ("TERMCHAR", "TERMCHAR_EN"): attribute = getattr(constants, "VI_ATTR_" + name) self.attrs[attribute] = attributes.AttributesByID[attribute].default def _get_timeout( self, attribute: constants.ResourceAttribute ) -> Tuple[int, StatusCode]: if self.interface: # 0x3 is the hexadecimal reference to the IbaTMO (timeout) configuration # option in linux-gpib. gpib_timeout = self.interface.ask(3) if gpib_timeout and gpib_timeout < len(TIMETABLE): self.timeout = TIMETABLE[gpib_timeout] else: # value is 0 or out of range -> infinite self.timeout = None return super(_GPIBCommon, self)._get_timeout(attribute) def _set_timeout(self, attribute: constants.ResourceAttribute, value: int): """Set the timeout 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 """ status = super(_GPIBCommon, self)._set_timeout(attribute, value) # Inspect the result of setting the value to decide how to translate the result # on the interface. if self.interface: if self.timeout is None: gpib_timeout = 0 else: # round up only values that are higher by 0.1% than discrete values gpib_timeout = min(bisect(TIMETABLE, 0.999 * self.timeout), 17) self.timeout = TIMETABLE[gpib_timeout] self.interface.timeout(gpib_timeout) return status def close(self) -> StatusCode: if self.interface: self.interface.close() self.controller.close() return StatusCode.success def read(self, count: int) -> Tuple[bytes, StatusCode]: """Reads data from device or interface synchronously. Corresponds to viRead function of the VISA library. Parameters ---------- count : int Number of bytes to be read. Returns ------- bytes Data read from the interface. StatusCode Return value of the library call. """ # INTFC don't have an interface so use the controller ifc = self.interface or self.controller # END 0x2000 checker = lambda current: ifc.ibsta() & 0x2000 # noqa: E731 reader = lambda: ifc.read(count) # noqa: E731 return self._read(reader, count, checker, False, None, False, gpib.GpibError) def write(self, data: bytes) -> Tuple[int, StatusCode]: """Writes data to device or interface synchronously. Corresponds to viWrite function of the VISA library. Parameters ---------- data : bytes Data to be written. Returns ------- int Number of bytes actually transferred StatusCode Return value of the library call. """ logger.debug("GPIB.write %r" % data) # INTFC don't have an interface so use the controller ifc = self.interface or self.controller try: ifc.write(data) count = ifc.ibcnt() # number of bytes transmitted return count, StatusCode.success except gpib.GpibError as e: return 0, convert_gpib_error(e, ifc.ibsta(), "write") def gpib_control_ren(self, mode: constants.RENLineOperation) -> StatusCode: """Controls the state of the GPIB Remote Enable (REN) interface line. Optionally the remote/local state of the device is also controlled. Corresponds to viGpibControlREN function of the VISA library. Parameters ---------- mode : constants.RENLineOperation Specifies the state of the REN line and optionally the device remote/local state. Returns ------- StatusCode Return value of the library call. """ if isinstance(self.parsed, GPIBIntfc): if mode not in ( constants.VI_GPIB_REN_ASSERT, constants.VI_GPIB_REN_DEASSERT, constants.VI_GPIB_REN_ASSERT_LLO, ): return constants.StatusCode.error_nonsupported_operation # INTFC don't have an interface so use the controller ifc = self.interface or self.controller try: if mode == constants.VI_GPIB_REN_DEASSERT_GTL: # Send GTL command byte (cf linux-gpib documentation) ifc.command(chr(1)) if mode in ( constants.VI_GPIB_REN_DEASSERT, constants.VI_GPIB_REN_DEASSERT_GTL, ): self.controller.remote_enable(0) if mode == constants.VI_GPIB_REN_ASSERT_LLO: # LLO ifc.command(b"0x11") elif mode == constants.VI_GPIB_REN_ADDRESS_GTL: # GTL ifc.command(b"0x1") elif mode == constants.VI_GPIB_REN_ASSERT_ADDRESS_LLO: pass elif mode in ( constants.VI_GPIB_REN_ASSERT, constants.VI_GPIB_REN_ASSERT_ADDRESS, ): ifc.remote_enable(1) if ( isinstance(self.parsed, GPIBInstr) and mode == constants.VI_GPIB_REN_ASSERT_ADDRESS ): # 0 for the secondary address means don't use it ifc.listener( self.parsed.primary_address, self.parsed.secondary_address ) except gpib.GpibError as e: return convert_gpib_error(e, self.interface.ibsta(), "perform control REN") return constants.StatusCode.success def _get_attribute(self, attribute: ResourceAttribute) -> Tuple[Any, StatusCode]: """Get the value for a given VISA attribute for this session. Use to implement custom logic for attributes. Parameters ---------- attribute : ResourceAttribute Attribute for which the state query is made Returns ------- Any The state of the queried attribute for a specified resource StatusCode Return value of the library call. """ # TODO implement the following attributes # - VI_ATTR_INTF_INST_NAME RO # - VI_ATTR_DMA_ALLOW_EN RW # - VI_ATTR_RD_BUF_OPER_MODE RW # - VI_ATTR_WR_BUF_OPER_MODE RW # - VI_ATTR_FILE_APPEND_EN RW # INTFC don't have an interface so use the controller ifc = self.interface or self.controller if attribute == ResourceAttribute.gpib_primary_address: # IbaPAD 0x1 return ifc.ask(1), StatusCode.success elif attribute == ResourceAttribute.gpib_secondary_address: # IbaSAD 0x2 # Remove 0x60 because National Instruments. _ = ifc.ask(2) if ifc.ask(2): return ifc.ask(2) - 96, StatusCode.success else: return constants.VI_NO_SEC_ADDR, StatusCode.success elif attribute == ResourceAttribute.gpib_ren_state: try: lines = self.controller.lines() return _analyse_lines_value(lines, attribute) except AttributeError: # some versions of linux-gpib do not expose Gpib.lines() return constants.VI_STATE_UNKNOWN, StatusCode.success elif attribute == ResourceAttribute.send_end_enabled: # Do not use IbaEndBitIsNormal 0x1a which relates to EOI on read() # not write(). see issue #196 # IbcEOT 0x4 if ifc.ask(4): return constants.VI_TRUE, StatusCode.success else: return constants.VI_FALSE, StatusCode.success elif attribute == ResourceAttribute.interface_number: # IbaBNA 0x200 return ifc.ask(512), StatusCode.success elif attribute == ResourceAttribute.interface_type: return constants.InterfaceType.gpib, StatusCode.success raise UnknownAttribute(attribute) def _set_attribute( self, attribute: constants.ResourceAttribute, attribute_state: Any ) -> StatusCode: """Sets the state of an attribute. Corresponds to viSetAttribute function of the VISA library. Parameters ---------- attribute : constants.ResourceAttribute Attribute for which the state is to be modified. (Attributes.*) attribute_state : Any The state of the attribute to be set for the specified object. Returns ------- StatusCode Return value of the library call. """ # TODO implement the following attributes # - VI_ATTR_DMA_ALLOW_EN RW # - VI_ATTR_RD_BUF_OPER_MODE RW # - VI_ATTR_WR_BUF_OPER_MODE RW # - VI_ATTR_FILE_APPEND_EN RW # INTFC don't have an interface so use the controller ifc = self.interface or self.controller if attribute == ResourceAttribute.gpib_readdress_enabled: # IbcREADDR 0x6 # Setting has no effect in linux-gpib. if isinstance(attribute_state, int): ifc.config(6, attribute_state) return StatusCode.success else: return StatusCode.error_nonsupported_attribute_state elif attribute == ResourceAttribute.gpib_primary_address: # IbcPAD 0x1 if isinstance(attribute_state, int) and 0 <= attribute_state <= 30: ifc.config(1, attribute_state) return StatusCode.success else: return StatusCode.error_nonsupported_attribute_state elif attribute == ResourceAttribute.gpib_secondary_address: # IbcSAD 0x2 # Add 0x60 because National Instruments. if isinstance(attribute_state, int) and 0 <= attribute_state <= 30: if ifc.ask(2): ifc.config(2, attribute_state + 96) return StatusCode.success else: return StatusCode.error_nonsupported_attribute else: return StatusCode.error_nonsupported_attribute_state elif attribute == ResourceAttribute.gpib_unadress_enable: # IbcUnAddr 0x1b try: ifc.config(27, attribute_state) return StatusCode.success except gpib.GpibError: return StatusCode.error_nonsupported_attribute_state elif attribute == ResourceAttribute.send_end_enabled: # Do not use IbaEndBitIsNormal 0x1a which relates to EOI on read() # not write(). see issue #196 # IbcEOT 0x4 if isinstance(attribute_state, int): ifc.config(4, attribute_state) return StatusCode.success else: return StatusCode.error_nonsupported_attribute_state raise UnknownAttribute(attribute) # TODO: Check secondary addresses. @Session.register(constants.InterfaceType.gpib, "INSTR") class GPIBSession(_GPIBCommon): """A GPIB Session that uses linux-gpib to do the low level communication.""" # Override parsed to take into account the fact that this class is only used # for a specific kind of resource parsed: GPIBInstr @staticmethod def list_resources() -> List[str]: return [ "GPIB%d::%d::INSTR" % (board, pad) if sad == 0 else "GPIB%d::%d::%d::INSTR" % (board, pad, sad - 0x60) for board, pad, sad in _find_listeners() ] def clear(self) -> StatusCode: """Clears a device. Corresponds to viClear function of the VISA library. Returns ------- StatusCode Return value of the library call. """ logger.debug("GPIB.device clear") try: self.interface.clear() return StatusCode.success except gpib.GpibError as e: return convert_gpib_error(e, self.interface.ibsta(), "clear") def assert_trigger(self, protocol: constants.TriggerProtocol) -> StatusCode: """Asserts hardware trigger. Parameters ---------- protocol : constants.TriggerProtocol Triggering protocol to use. Only supports constants.TriggerProtocol.default Returns ------- StatusCode Return value of the library call. """ logger.debug("GPIB.device assert hardware trigger") try: if protocol == constants.VI_TRIG_PROT_DEFAULT: self.interface.trigger() return StatusCode.success else: return StatusCode.error_nonsupported_operation except gpib.GpibError as e: return convert_gpib_error(e, self.interface.ibsta(), "assert trigger") def read_stb(self) -> Tuple[int, StatusCode]: """Read the device status byte.""" try: return self.interface.serial_poll(), StatusCode.success except gpib.GpibError as e: return 0, convert_gpib_error(e, self.interface.ibsta(), "read STB") def _get_attribute( self, attribute: constants.ResourceAttribute ) -> Tuple[Any, StatusCode]: """Get the value for a given VISA attribute for this session. Use to implement custom logic for attributes. GPIB::INSTR have the following specific attributes: - VI_ATTR_TRIG_ID - VI_ATTR_IO_PROT - VI_ATTR_SUPPRESS_END_EN - VI_ATTR_GPIB_READDR_EN - VI_ATTR_GPIB_UNADDR_EN Parameters ---------- attribute : constants.ResourceAttribute Resource attribute for which the state query is made Returns ------- Any State of the queried attribute for a specified resource StatusCode Return value of the library call. """ # TODO implement the following attributes # - VI_ATTR_TRIG_ID RW or RO see specs # - VI_ATTR_IO_PROT RW # - VI_ATTR_SUPPRESS_END_EN RW ifc = self.interface if attribute == constants.VI_ATTR_GPIB_READDR_EN: # IbaREADDR 0x6 # Setting has no effect in linux-gpib. return ifc.ask(6), StatusCode.success elif attribute == constants.VI_ATTR_GPIB_UNADDR_EN: # IbaUnAddr 0x1b if ifc.ask(27): return constants.VI_TRUE, StatusCode.success else: return constants.VI_FALSE, StatusCode.success return super(GPIBSession, self)._get_attribute(attribute) def _set_attribute( self, attribute: ResourceAttribute, attribute_state: Any ) -> StatusCode: """Sets the state of an attribute. Corresponds to viSetAttribute function of the VISA library. Parameters ---------- attribute : ResourceAttribute Attribute for which the state is to be modified. (Attributes.*) attribute_state : Any The state of the attribute to be set for the specified object. Returns ------- StatusCode Return value of the library call. """ # TODO implement the following attributes # - VI_ATTR_TRIG_ID RW or RO see specs # - VI_ATTR_IO_PROT RW # - VI_ATTR_SUPPRESS_END_EN RW ifc = self.interface if attribute == constants.VI_ATTR_GPIB_READDR_EN: # IbcREADDR 0x6 # Setting has no effect in linux-gpib. if isinstance(attribute_state, int): ifc.config(6, attribute_state) return StatusCode.success else: return StatusCode.error_nonsupported_attribute_state elif attribute == constants.VI_ATTR_GPIB_UNADDR_EN: # IbcUnAddr 0x1b try: ifc.config(27, attribute_state) return StatusCode.success except gpib.GpibError: return StatusCode.error_nonsupported_attribute_state return super(GPIBSession, self)._set_attribute(attribute, attribute_state) @Session.register(constants.InterfaceType.gpib, "INTFC") class GPIBInterface(_GPIBCommon): """A GPIB Interface that uses linux-gpib to do the low level communication.""" # Override parsed to take into account the fact that this class is only used # for a specific kind of resource parsed: GPIBIntfc @staticmethod def list_resources() -> List[str]: return ["GPIB%d::INTFC" % board for board, pad in _find_boards()] def gpib_command(self, command_bytes: bytes) -> Tuple[int, StatusCode]: """Write GPIB command byte on the bus. Corresponds to viGpibCommand function of the VISA library. See: https://linux-gpib.sourceforge.io/doc_html/gpib-protocol.html#REFERENCE-COMMAND-BYTES Parameters ---------- command_bytes : bytes Command bytes to send Returns ------- int Number of written bytes, StatusCode Return value of the library call. """ try: return self.controller.command(command_bytes), StatusCode.success except gpib.GpibError as e: return 0, convert_gpib_error(e, self.controller.ibsta(), "gpib command") def gpib_send_ifc(self) -> StatusCode: """Pulse the interface clear line (IFC) for at least 100 microseconds. Corresponds to viGpibSendIFC function of the VISA library. """ logger.debug("GPIB.interface clear") try: self.controller.interface_clear() return StatusCode.success except gpib.GpibError as e: return convert_gpib_error(e, self.controller.ibsta(), "send IFC") def gpib_control_atn(self, mode: constants.ATNLineOperation) -> StatusCode: """Specifies the state of the ATN line and the local active controller state. Corresponds to viGpibControlATN function of the VISA library. Parameters ---------- mode : constants.ATNLineOperation Specifies the state of the ATN line and optionally the local active controller state. Returns ------- StatusCode Return value of the library call. """ logger.debug("GPIB.control atn") if mode == constants.VI_GPIB_ATN_ASSERT: status = gpib_lib.ibcac(self.controller.id, 0) elif mode == constants.VI_GPIB_ATN_DEASSERT: status = gpib_lib.ibgts(self.controller.id, 0) elif mode == constants.VI_GPIB_ATN_ASSERT_IMMEDIATE: # Asynchronous assertion (the name is counter intuitive) status = gpib_lib.ibcac(self.controller.id, 1) elif mode == constants.VI_GPIB_ATN_DEASSERT_HANDSHAKE: status = gpib_lib.ibgts(self.controller.id, 1) else: return constants.StatusCode.error_invalid_mode return convert_gpib_status(status) def gpib_pass_control( self, primary_address: int, secondary_address: int ) -> StatusCode: """Tell a GPIB device to become controller in charge (CIC). Corresponds to viGpibPassControl function of the VISA library. Parameters ---------- primary_address : int Primary address of the GPIB device to which you want to pass control. secondary_address : int Secondary address of the targeted GPIB device. If the targeted device does not have a secondary address, this parameter should contain the value Constants.VI_NO_SEC_ADDR. Returns ------- StatusCode Return value of the library call. """ # ibpct need to get the device id matching the primary and secondary address logger.debug("GPIB.pass control") try: did = gpib.dev(self.parsed.board, primary_address, secondary_address) except gpib.GpibError: logger.exception( "Failed to get id for %s, %d", primary_address, secondary_address ) return StatusCode.error_resource_not_found status = gpib_lib.ibpct(did) return convert_gpib_status(status) def _get_attribute(self, attribute: ResourceAttribute) -> Tuple[Any, StatusCode]: """Get the value for a given VISA attribute for this session. Use to implement custom logic for attributes. GPIB::INTFC have the following specific attributes: - VI_ATTR_DEV_STATUS_BYTE - VI_ATTR_GPIB_ATN_STATE - VI_ATTR_GPIB_NDAC_STATE - VI_ATTR_GPIB_SRQ_STATE - VI_ATTR_GPIB_CIC_STATE - VI_ATTR_GPIB_SYS_CNTRL_STATE - VI_ATTR_GPIB_HS488_CBL_LEN - VI_ATTR_GPIB_ADDR_STATE Parameters ---------- attribute: ResourceAttribute Resource attribute for which the state query is made. Returns ------- Any The state of the queried attribute for a specified resource, StatusCode Return value of the library call. """ # TODO implement the following attributes # - VI_ATTR_DEV_STATUS_BYTE RW # - VI_ATTR_GPIB_SYS_CNTRL_STATE RW # - VI_ATTR_GPIB_HS488_CBL_LEN RO # - VI_ATTR_GPIB_ADDR_STATE RO ifc = self.controller if attribute == constants.VI_ATTR_GPIB_CIC_STATE: # ibsta CIC = 0x0020 if ifc.ibsta() & 0x0020: return constants.VI_TRUE, StatusCode.success else: return constants.VI_FALSE, StatusCode.success elif attribute in ( constants.VI_ATTR_GPIB_ATN_STATE, constants.VI_ATTR_GPIB_NDAC_STATE, constants.VI_ATTR_GPIB_SRQ_STATE, ): try: lines = ifc.lines() return _analyse_lines_value(lines, attribute) except AttributeError: # some versions of linux-gpib do not expose Gpib.lines() return constants.VI_STATE_UNKNOWN, StatusCode.success return super()._get_attribute(attribute) def _set_attribute( self, attribute: ResourceAttribute, attribute_state: Any ) -> StatusCode: """Sets the state of an attribute. Corresponds to viSetAttribute function of the VISA library. Parameters ---------- attribute : ResourceAttribute Attribute for which the state is to be modified. attribute_state : Any The state of the attribute to be set for the specified object. Returns ------- StatusCode Return value of the library call. """ # TODO implement the following attributes # - VI_ATTR_GPIB_SYS_CNTRL_STATE # - VI_ATTR_DEV_STATUS_BYTE # INTFC don't have an interface so use the controller _ = self.controller return super()._set_attribute(attribute, attribute_state) pyvisa-py-0.7.2/pyvisa_py/highlevel.py000066400000000000000000000607671457232645700200640ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Highlevel wrapper of the VISA Library. :copyright: 2014-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ import random from collections import OrderedDict from typing import Any, Dict, Iterable, List, Optional, Tuple, Union, cast from pyvisa import constants, highlevel, rname from pyvisa.constants import StatusCode from pyvisa.typing import VISAEventContext, VISARMSession, VISASession from pyvisa.util import DebugInfo, LibraryPath 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 agnostic 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. """ #: Live session object identified by a randon session ID sessions: Dict[int, sessions.Session] # Try to import packages implementing lower level functionality. try: from .serial import SerialSession logger.debug("SerialSession was correctly imported.") except Exception as e: logger.debug("SerialSession was not imported %s." % e) try: from .usb import USBRawSession, USBSession logger.debug("USBSession and USBRawSession were correctly imported.") except Exception as e: logger.debug("USBSession and USBRawSession were not imported %s." % e) try: from .tcpip import TCPIPInstrSession, TCPIPSocketSession logger.debug("TCPIPSession was correctly imported.") except Exception as e: logger.debug("TCPIPSession was not imported %s." % e) try: from .gpib import GPIBSession logger.debug("GPIBSession was correctly imported.") except Exception as e: logger.debug("GPIBSession was not imported %s." % e) @staticmethod def get_library_paths() -> Iterable[LibraryPath]: """List a dummy library path to allow to create the library.""" return (LibraryPath("py"),) @staticmethod def get_debug_info() -> DebugInfo: """Return a list of lines with backend info.""" from . import __version__ d: OrderedDict[str, Union[str, List[str], Dict[str, str]]] = OrderedDict() d["Version"] = "%s" % __version__ for key, val in sessions.Session.iter_valid_session_classes(): key_name = "%s %s" % (key[0].name.upper(), key[1]) d[key_name] = "Available " + val.get_low_level_info() for key, issue in sessions.Session.iter_session_classes_issues(): key_name = "%s %s" % (key[0].name.upper(), key[1]) d[key_name] = issue.split("\n") return d def _init(self) -> None: """Custom initialization code.""" # Map session handle to session object. self.sessions = {} def _register(self, obj: object) -> VISASession: """Creates a random but unique session handle for a session object. Register it in the sessions dictionary and return the value. """ session = None while session is None or session in self.sessions: session = random.randint(1000000, 9999999) self.sessions[session] = obj return session def open( self, session: VISARMSession, resource_name: str, access_mode: constants.AccessModes = constants.AccessModes.no_lock, open_timeout: Optional[int] = constants.VI_TMO_IMMEDIATE, ) -> Tuple[VISASession, StatusCode]: """Opens a session to the specified resource. Corresponds to viOpen function of the VISA library. Parameters ---------- session : VISARMSession Resource Manager session (should always be a session returned from open_default_resource_manager()). resource_name : str Unique symbolic name of a resource. access_mode : constants.AccessModes, optional Specifies the mode by which the resource is to be accessed. open_timeout : int Specifies the maximum time period (in milliseconds) that this operation waits before returning an error. constants.VI_TMO_IMMEDIATE and constants.VI_TMO_INFINITE are used as min and max. Returns ------- VISASession Unique logical identifier reference to a session StatusCode Return value of the library call. """ try: open_timeout = None if open_timeout is None else 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 ( VISASession(0), self.handle_return_value(None, StatusCode.error_invalid_resource_name), ) cls = sessions.Session.get_session_class( parsed.interface_type_const, parsed.resource_class ) sess = cls(session, resource_name, parsed, open_timeout) return self._register(sess), StatusCode.success def clear(self, session: VISASession) -> StatusCode: """Clears a device. Corresponds to viClear function of the VISA library. Parameters ---------- session : typing.VISASession Unique logical identifier to a session. Returns ------- StatusCode Return value of the library call. """ try: sess = self.sessions[session] except KeyError: return self.handle_return_value(session, StatusCode.error_invalid_object) return self.handle_return_value(session, sess.clear()) def flush( self, session: VISASession, mask: constants.BufferOperation ) -> StatusCode: """Flush the specified buffers. The buffers can be associated with formatted I/O operations and/or serial communication. Corresponds to viFlush function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. mask : constants.BufferOperation Specifies the action to be taken with flushing the buffer. The values can be combined using the | operator. However multiple operations on a single buffer cannot be combined. Returns ------- StatusCode Return value of the library call. """ try: sess = self.sessions[session] except KeyError: return self.handle_return_value(session, StatusCode.error_invalid_object) return self.handle_return_value(session, sess.flush(mask)) def gpib_command( self, session: VISASession, command_byte: bytes ) -> Tuple[int, StatusCode]: """Write GPIB command bytes on the bus. Corresponds to viGpibCommand function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. command_byte : bytes Data to write. Returns ------- int Number of written bytes StatusCode Return value of the library call. """ try: written, st = self.sessions[session].gpib_command(command_byte) return written, self.handle_return_value(session, st) except KeyError: return 0, self.handle_return_value(session, StatusCode.error_invalid_object) def assert_trigger( self, session: VISASession, protocol: constants.TriggerProtocol ) -> StatusCode: """Assert software or hardware trigger. Corresponds to viAssertTrigger function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. protocol : constants.TriggerProtocol Trigger protocol to use during assertion. Returns ------- StatusCode Return value of the library call. """ try: return self.handle_return_value( session, self.sessions[session].assert_trigger(protocol) ) except KeyError: return self.handle_return_value(session, StatusCode.error_invalid_object) def gpib_send_ifc(self, session: VISASession) -> StatusCode: """Pulse the interface clear line (IFC) for at least 100 microseconds. Corresponds to viGpibSendIFC function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. Returns ------- StatusCode Return value of the library call. """ try: return self.handle_return_value( session, self.sessions[session].gpib_send_ifc() ) except KeyError: return self.handle_return_value(session, StatusCode.error_invalid_object) def gpib_control_ren( self, session: VISASession, mode: constants.RENLineOperation ) -> StatusCode: """Controls the state of the GPIB Remote Enable (REN) interface line. Optionally the remote/local state of the device can also be set. Corresponds to viGpibControlREN function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. mode : constants.RENLineOperation State of the REN line and optionally the device remote/local state. Returns ------- StatusCode Return value of the library call. """ try: return self.handle_return_value( session, self.sessions[session].gpib_control_ren(mode) ) except KeyError: return self.handle_return_value(session, StatusCode.error_invalid_object) def gpib_control_atn( self, session: VISASession, mode: constants.ATNLineOperation ) -> StatusCode: """Specifies the state of the ATN line and the local active controller state. Corresponds to viGpibControlATN function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. mode : constants.ATNLineOperation State of the ATN line and optionally the local active controller state. Returns ------- StatusCode Return value of the library call. """ try: return self.handle_return_value( session, self.sessions[session].gpib_control_atn(mode) ) except KeyError: return self.handle_return_value(session, StatusCode.error_invalid_object) def gpib_pass_control( self, session: VISASession, primary_address: int, secondary_address: int ) -> StatusCode: """Tell a GPIB device to become controller in charge (CIC). Corresponds to viGpibPassControl function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. primary_address : int Primary address of the GPIB device to which you want to pass control. secondary_address : int Secondary address of the targeted GPIB device. If the targeted device does not have a secondary address, this parameter should contain the value Constants.VI_NO_SEC_ADDR. Returns ------- StatusCode Return value of the library call. """ try: return self.handle_return_value( session, self.sessions[session].gpib_pass_control( primary_address, secondary_address ), ) except KeyError: return self.handle_return_value(session, StatusCode.error_invalid_object) def read_stb(self, session: VISASession) -> Tuple[int, StatusCode]: """Reads a status byte of the service request. Corresponds to viReadSTB function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. Returns ------- int Service request status byte StatusCode Return value of the library call. """ try: sess = self.sessions[session] except KeyError: return 0, self.handle_return_value(session, StatusCode.error_invalid_object) stb, status_code = sess.read_stb() return stb, self.handle_return_value(session, status_code) def close( self, session: Union[VISASession, VISAEventContext, VISARMSession] ) -> StatusCode: """Closes the specified session, event, or find list. Corresponds to viClose function of the VISA library. Parameters --------- session : Union[VISASession, VISAEventContext, VISARMSession] Unique logical identifier to a session, event, resource manager. Returns ------- StatusCode Return value of the library call. """ try: sess = self.sessions[session] # The RM session directly references the library. if sess is not self: return self.handle_return_value(session, sess.close()) else: return self.handle_return_value(session, StatusCode.success) except KeyError: return self.handle_return_value(session, StatusCode.error_invalid_object) def open_default_resource_manager(self) -> Tuple[VISARMSession, StatusCode]: """This function returns a session to the Default Resource Manager resource. Corresponds to viOpenDefaultRM function of the VISA library. Returns ------- VISARMSession Unique logical identifier to a Default Resource Manager session StatusCode Return value of the library call. """ return ( cast(VISARMSession, self._register(self)), self.handle_return_value(None, StatusCode.success), ) def list_resources( self, session: VISARMSession, query: str = "?*::INSTR" ) -> Tuple[str, ...]: """Return a tuple of all connected devices matching query. Parameters ---------- session : VISARMSession Unique logical identifier to the resource manager session. query : str Regular expression used to match devices. Returns ------- Tuple[str, ...] Resource names of all the connected devices matching the query. """ # For each session type, ask for the list of connected resources and # merge them into a single list. # HINT: the cast should not be necessary here resources: List[str] = [] for key, st in sessions.Session.iter_valid_session_classes(): resources += st.list_resources() return rname.filter(resources, query) def read(self, session: VISASession, count: int) -> Tuple[bytes, StatusCode]: """Reads data from device or interface synchronously. Corresponds to viRead function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. count : int Number of bytes to be read. Returns ------- bytes Date read StatusCode Return value of the library call. """ # from the session handle, dispatch to the read method of the session object. try: data, status_code = self.sessions[session].read(count) except KeyError: return ( b"", self.handle_return_value(session, StatusCode.error_invalid_object), ) return data, self.handle_return_value(session, status_code) def write(self, session: VISASession, data: bytes) -> Tuple[int, StatusCode]: """Write data to device or interface synchronously. Corresponds to viWrite function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. data : bytes Data to be written. Returns ------- int Number of bytes actually transferred StatusCode Return value of the library call. """ # from the session handle, dispatch to the write method of the session object. try: written, status_code = self.sessions[session].write(data) except KeyError: return 0, self.handle_return_value(session, StatusCode.error_invalid_object) return written, self.handle_return_value(session, status_code) def buffer_read(self, session: VISASession, count: int) -> Tuple[bytes, StatusCode]: """Reads data through the use of a formatted I/O read buffer. The data can be read from a device or an interface. Corresponds to viBufRead function of the VISA library. Parameters ---------- session : VISASession\ Unique logical identifier to a session. count : int Number of bytes to be read. Returns ------- bytes Data read StatusCode Return value of the library call. """ return self.read(session, count) def buffer_write(self, session: VISASession, data: bytes) -> Tuple[int, StatusCode]: """Writes data to a formatted I/O write buffer synchronously. Corresponds to viBufWrite function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. data : bytes Data to be written. Returns ------- int number of written bytes StatusCode return value of the library call. """ return self.write(session, data) def get_attribute( self, session: Union[VISASession, VISAEventContext, VISARMSession], attribute: Union[constants.ResourceAttribute, constants.EventAttribute], ) -> Tuple[Any, StatusCode]: """Retrieves the state of an attribute. Corresponds to viGetAttribute function of the VISA library. Parameters ---------- session : Union[VISASession, VISAEventContext] Unique logical identifier to a session, event, or find list. attribute : Union[constants.ResourceAttribute, constants.EventAttribute] Resource or event attribute for which the state query is made. Returns ------- Any State of the queried attribute for a specified resource StatusCode Return value of the library call. """ try: sess = self.sessions[session] except KeyError: return ( None, self.handle_return_value(session, StatusCode.error_invalid_object), ) state, status_code = sess.get_attribute( cast(constants.ResourceAttribute, attribute) ) return state, self.handle_return_value(session, status_code) def set_attribute( self, session: VISASession, attribute: constants.ResourceAttribute, attribute_state: Any, ) -> StatusCode: """Set the state of an attribute. Corresponds to viSetAttribute function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. attribute : constants.ResourceAttribute Attribute for which the state is to be modified. attribute_state : Any The state of the attribute to be set for the specified object. Returns ------- StatusCode Return value of the library call. """ try: return self.handle_return_value( session, self.sessions[session].set_attribute(attribute, attribute_state), ) except KeyError: return self.handle_return_value(session, StatusCode.error_invalid_object) def lock( self, session: VISASession, lock_type: constants.Lock, timeout: int, requested_key: Optional[str] = None, ) -> Tuple[str, StatusCode]: """Establishes an access mode to the specified resources. Corresponds to viLock function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. lock_type : constants.Lock Specifies the type of lock requested. timeout : int Absolute time period (in milliseconds) that a resource waits to get unlocked by the locking session before returning an error. requested_key : Optional[str], optional Requested locking key in the case of a shared lock. For an exclusive lock it should be None. Returns ------- str Key that can then be passed to other sessions to share the lock, or None for an exclusive lock. StatusCode Return value of the library call. """ try: sess = self.sessions[session] except KeyError: return ( "", self.handle_return_value(session, StatusCode.error_invalid_object), ) key, status_code = sess.lock(lock_type, timeout, requested_key) return key, self.handle_return_value(session, status_code) def unlock(self, session: VISASession) -> StatusCode: """Relinquish a lock for the specified resource. Corresponds to viUnlock function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. Returns ------- StatusCode Return value of the library call. """ try: sess = self.sessions[session] except KeyError: return self.handle_return_value(session, StatusCode.error_invalid_object) return self.handle_return_value(session, sess.unlock()) def disable_event( self, session: VISASession, event_type: constants.EventType, mechanism: constants.EventMechanism, ) -> StatusCode: """Disable notification for an event type(s) via the specified mechanism(s). Corresponds to viDisableEvent function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. event_type : constants.EventType Event type. mechanism : constants.EventMechanism Event handling mechanisms to be disabled. Returns ------- StatusCode Return value of the library call. """ return StatusCode.error_nonimplemented_operation def discard_events( self, session: VISASession, event_type: constants.EventType, mechanism: constants.EventMechanism, ) -> StatusCode: """Discard event occurrences for a given type and mechanisms in a session. Corresponds to viDiscardEvents function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. event_type : constans.EventType Logical event identifier. mechanism : constants.EventMechanism Specifies event handling mechanisms to be discarded. Returns ------- StatusCode Return value of the library call. """ return StatusCode.error_nonimplemented_operation pyvisa-py-0.7.2/pyvisa_py/protocols/000077500000000000000000000000001457232645700175475ustar00rootroot00000000000000pyvisa-py-0.7.2/pyvisa_py/protocols/__init__.py000066400000000000000000000003461457232645700216630ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Implements protocols on top of lower level libraries to talk to instruments. :copyright: 2014-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ pyvisa-py-0.7.2/pyvisa_py/protocols/hislip.py000066400000000000000000000645051457232645700214230ustar00rootroot00000000000000""" Python implementation of HiSLIP protocol. Based on the HiSLIP spec: http://www.ivifoundation.org/downloads/Class%20Specifications/IVI-6.1_HiSLIP-1.1-2011-02-24.pdf """ import socket import struct import time from typing import Dict, Optional, Tuple PORT = 4880 MESSAGETYPE_STR: Dict[int, str] = { 0: "Initialize", 1: "InitializeResponse", 2: "FatalError", 3: "Error", 4: "AsyncLock", 5: "AsyncLockResponse", 6: "Data", 7: "DataEnd", 8: "DeviceClearComplete", 9: "DeviceClearAcknowledge", 10: "AsyncRemoteLocalControl", 11: "AsyncRemoteLocalResponse", 12: "Trigger", 13: "Interrupted", 14: "AsyncInterrupted", 15: "AsyncMaxMsgSize", 16: "AsyncMaxMsgSizeResponse", 17: "AsyncInitialize", 18: "AsyncInitializeResponse", 19: "AsyncDeviceClear", 20: "AsyncServiceRequest", 21: "AsyncStatusQuery", 22: "AsyncStatusResponse", 23: "AsyncDeviceClearAcknowledge", 24: "AsyncLockInfo", 25: "AsyncLockInfoResponse", 26: "GetDescriptors", 27: "GetDescriptorsResponse", 28: "StartTLS", 29: "AsyncStartTLS", 30: "AsyncStartTLSResponse", 31: "EndTLS", 32: "AsyncEndTLS", 33: "AsyncEndTLSResponse", 34: "GetSaslMechanismList", 35: "GetSaslMechanismListResponse", 36: "AuthenticationStart", 37: "AuthenticationExchange", 38: "AuthenticationResult", # reserved for future use 39-127 inclusive # VendorSpecific 128-255 inclusive } MESSAGETYPE: Dict[str, int] = {value: key for (key, value) in MESSAGETYPE_STR.items()} FATALERRORMESSAGE: Dict[int, str] = { 0: "Unidentified error", 1: "Poorly formed message header", 2: "Attempt to use connection without both channels established", 3: "Invalid Initialization sequence", 4: "Server refused connection due to maximum number of clients exceeded", 5: "Secure connection failed", # 6-127: reserved for HiSLIP extensions # 128-255: device defined errors } FATALERRORCODE: Dict[str, int] = { value: key for (key, value) in FATALERRORMESSAGE.items() } ERRORMESSAGE: Dict[int, str] = { 0: "Unidentified error", 1: "Unrecognized Message Type", 2: "Unrecognized control code", 3: "Unrecognized Vendor Defined Message", 4: "Message too large", 5: "Authentication failed", # 6-127: Reserved # 128-255: Device defined errors } ERRORCODE: Dict[str, int] = {value: key for (key, value) in ERRORMESSAGE.items()} LOCKCONTROLCODE: Dict[str, int] = { "release": 0, "request": 1, } LOCKRESPONSE: Dict[int, str] = { 0: "failure", 1: "success", # or "success exclusive" 2: "success shared", 3: "error", } REMOTELOCALCONTROLCODE: Dict[str, int] = { "disableRemote": 0, "enableRemote": 1, "disableAndGTL": 2, "enableAndGotoRemote": 3, "enableAndLockoutLocal": 4, "enableAndGTRLLO": 5, "justGTL": 6, } HEADER_FORMAT = "!2sBBIQ" # ! = network order, # 2s = prologue ('HS'), # B = message type (unsigned byte), # B = control code (unsigned byte), # I = message parameter (unsigned int), # Q = payload length (unsigned long long) HEADER_SIZE = struct.calcsize(HEADER_FORMAT) DEFAULT_MAX_MSG_SIZE = 1 << 20 # from VISA spec ######################################################################################### def receive_flush(sock: socket.socket, recv_len: int) -> None: """ receive exactly 'recv_len' bytes from 'sock'. no explicit timeout is specified, since it is assumed that a call to select indicated that data is available. received data is thrown away and nothing is returned """ # limit the size of the recv_buffer to something moderate # in order to limit the impact on virtual memory recv_buffer = bytearray(min(1 << 20, recv_len)) bytes_recvd = 0 while bytes_recvd < recv_len: request_size = min(len(recv_buffer), recv_len - bytes_recvd) data_len = sock.recv_into(recv_buffer, request_size) bytes_recvd += data_len def receive_exact(sock: socket.socket, recv_len: int) -> bytes: """ receive exactly 'recv_len' bytes from 'sock'. no explicit timeout is specified, since it is assumed that a call to select indicated that data is available. returns a bytearray containing the received data. """ recv_buffer = bytearray(recv_len) receive_exact_into(sock, recv_buffer) return recv_buffer def receive_exact_into(sock: socket.socket, recv_buffer: bytes) -> None: """ receive data from 'sock' to exactly fill 'recv_buffer'. no explicit timeout is specified, since it is assumed that a call to select indicated that data is available. """ view = memoryview(recv_buffer) recv_len = len(recv_buffer) bytes_recvd = 0 while bytes_recvd < recv_len: request_size = recv_len - bytes_recvd data_len = sock.recv_into(view, request_size) bytes_recvd += data_len view = view[data_len:] if bytes_recvd > recv_len: raise MemoryError("socket.recv_into scribbled past end of recv_buffer") def send_msg( sock: socket.socket, msg_type: str, control_code: int, message_parameter: Optional[int], payload: bytes = b"", ) -> None: """Send a message on sock w/ payload.""" msg = bytearray( struct.pack( HEADER_FORMAT, b"HS", MESSAGETYPE[msg_type], control_code, message_parameter or 0, len(payload), ) ) # txdecode(msg, payload) # uncomment for debugging msg.extend(payload) sock.sendall(msg) class RxHeader: """Generic base class for receiving messages. specific protocol responses subclass this class. """ def __init__( self, sock: socket.socket, expected_message_type: Optional[str] = None, ) -> None: """receive and decode the HiSLIP message header""" self.header = receive_exact(sock, HEADER_SIZE) # rxdecode(self.header) # uncomment for debugging ( prologue, msg_type, self.control_code, self.message_parameter, self.payload_length, ) = struct.unpack(HEADER_FORMAT, self.header) if prologue != b"HS": # XXX we should send a 'Fatal Error' to the server, close the # sockets, then raise an exception raise RuntimeError("protocol synchronization error") if msg_type not in MESSAGETYPE_STR: # XXX we should send 'Unrecognized message type' to the # server and discard this packet plus any payload. raise RuntimeError("unrecognized message type: %d" % msg_type) self.msg_type = MESSAGETYPE_STR[msg_type] if expected_message_type is not None and self.msg_type != expected_message_type: # XXX we should send an 'Error: Unidentified Error' to the server # and discard this packet plus any payload payload = ( (": " + str(receive_exact(sock, self.payload_length))) if self.payload_length > 0 else b"" ) raise RuntimeError( "expected message type '%s', received '%s%s'" % (expected_message_type, self.msg_type, payload) ) if self.msg_type == "DataEnd" or self.msg_type == "Data": assert self.control_code == 0 self.message_id = self.message_parameter class InitializeResponse(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "InitializeResponse") assert self.payload_length == 0 self.overlap = bool(self.control_code) self.version, self.session_id = struct.unpack("!4xHH8x", self.header) class AsyncInitializeResponse(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "AsyncInitializeResponse") assert self.control_code == 0 assert self.payload_length == 0 self.vendor_id = struct.unpack("!4x4s8x", self.header) class AsyncMaxMsgSizeResponse(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "AsyncMaxMsgSizeResponse") assert self.control_code == 0 assert self.message_parameter == 0 assert self.payload_length == 8 payload = receive_exact(sock, self.payload_length) self.max_msg_size = struct.unpack("!Q", payload)[0] class AsyncDeviceClearAcknowledge(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "AsyncDeviceClearAcknowledge") self.feature_bitmap = self.control_code assert self.message_parameter == 0 assert self.payload_length == 0 class AsyncInterrupted(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "AsyncInterrupted") assert self.control_code == 0 self.message_id = self.message_parameter assert self.payload_length == 0 class AsyncLockInfoResponse(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "AsyncLockInfoResponse") self.exclusive_lock = self.control_code # 0: no lock, 1: lock granted self.clients_holding_locks = self.message_parameter assert self.payload_length == 0 class AsyncLockResponse(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "AsyncLockResponse") self.lock_response = LOCKRESPONSE[self.control_code] assert self.message_parameter == 0 assert self.payload_length == 0 class AsyncRemoteLocalResponse(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "AsyncRemoteLocalResponse") assert self.control_code == 0 assert self.message_parameter == 0 assert self.payload_length == 0 class AsyncServiceRequest(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "AsyncServiceRequest") self.server_status = self.control_code assert self.message_parameter == 0 assert self.payload_length == 0 class AsyncStatusResponse(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "AsyncStatusResponse") self.server_status = self.control_code assert self.message_parameter == 0 assert self.payload_length == 0 class DeviceClearAcknowledge(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "DeviceClearAcknowledge") self.feature_bitmap = self.control_code assert self.message_parameter == 0 assert self.payload_length == 0 class Interrupted(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "Interrupted") assert self.control_code == 0 self.message_id = self.message_parameter assert self.payload_length == 0 class Error(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "Error") self.error_code = ERRORMESSAGE[self.control_code] assert self.message_parameter == 0 self.error_message = receive_exact(sock, self.payload_length) class FatalError(RxHeader): def __init__(self, sock: socket.socket) -> None: super().__init__(sock, "FatalError") self.error_code = FATALERRORMESSAGE[self.control_code] assert self.message_parameter == 0 self.error_message = receive_exact(sock, self.payload_length) class Instrument: """ this is the principal export from this module. it opens up a HiSLIP connection to the instrument at the specified IP address. """ def __init__( self, ip_addr: str, timeout: Optional[float] = None, port: int = PORT, sub_address: str = "hislip0", ) -> None: # init transaction: # C->S: Initialize # S->C: InitializeResponse # C->S: AsyncInitialize # S->C: AsyncInitializeResponse timeout = timeout or 5.0 # open the synchronous socket and send an initialize packet self._sync = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._sync.connect((ip_addr, port)) self._sync.settimeout(timeout) self._sync.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) init = self.initialize(sub_address=sub_address.encode("ascii")) if init.overlap != 0: print("**** prefer overlap = %d" % init.overlap) # open the asynchronous socket and send an initialize packet self._async = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._async.connect((ip_addr, port)) self._async.settimeout(timeout) self._async.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self._async_init = self.async_initialize(session_id=init.session_id) # initialize variables self.max_msg_size = DEFAULT_MAX_MSG_SIZE self.keepalive = False self.timeout = timeout self._rmt = 0 self._message_id = 0xFFFF_FF00 self._last_message_id: Optional[int] = None self._msg_type: str = "" self._payload_remaining: int = 0 # ================ # # MEMBER FUNCTIONS # # ================ # def close(self) -> None: self._sync.close() self._async.close() @property def timeout(self) -> float: """Timeout value in seconds for both the sync and async sockets""" return self._timeout @timeout.setter def timeout(self, val: float) -> None: """Timeout value in seconds for both the sync and async sockets""" self._timeout = val self._sync.settimeout(self._timeout) self._async.settimeout(self._timeout) @property def max_msg_size(self) -> int: """Maximum HiSLIP message size in bytes.""" return self._max_msg_size @max_msg_size.setter def max_msg_size(self, size: int) -> None: self._max_msg_size = self.async_maximum_message_size(size) @property def last_message_id(self) -> Optional[int]: return self._last_message_id @last_message_id.setter def last_message_id(self, message_id: Optional[int]) -> None: """Re-set last message id and related attributes""" self._last_message_id = message_id self._rmt = 0 self._payload_remaining = 0 self._msg_type = "" @property def keepalive(self) -> bool: """Status of the TCP keepalive. Keepalive is on/off for both the sync and async sockets If a connection is dropped as a result of “keepalives”, the error code VI_ERROR_CONN_LOST is returned to current and subsequent I/O calls on the session. """ return self._keepalive @keepalive.setter def keepalive(self, keepalive: bool) -> None: self._keepalive = bool(keepalive) self._sync.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, bool(keepalive)) self._async.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, bool(keepalive)) def send(self, data: bytes) -> int: """Send the data on the synchronous channel. More than one packet may be necessary in order to not exceed max_payload_size. """ # print(f"send({data=})") # uncomment for debugging data_view = memoryview(data) num_bytes_to_send = len(data) max_payload_size = self._max_msg_size - HEADER_SIZE # send the data in chunks of max_payload_size bytes at a time while num_bytes_to_send > 0: if num_bytes_to_send <= max_payload_size: assert len(data_view) == num_bytes_to_send self._send_data_end_packet(data_view) bytes_sent = num_bytes_to_send else: self._send_data_packet(data_view[:max_payload_size]) bytes_sent = max_payload_size data_view = data_view[bytes_sent:] num_bytes_to_send -= bytes_sent return len(data) def receive(self, max_len: int = 4096) -> bytes: """Receive data on the synchronous channel. Terminate after max_len bytes or after receiving a DataEnd message """ # print(f"receive({max_len=})") # uncomment for debugging # receive data, terminating after len(recv_buffer) bytes or # after receiving a DataEnd message. # # note the use of receive_exact_into (which calls socket.recv_into), # avoiding unnecessary copies. # recv_buffer = bytearray(max_len) view = memoryview(recv_buffer) bytes_recvd = 0 while bytes_recvd < max_len: if self._payload_remaining <= 0: if self._msg_type == "DataEnd": # truncate to the actual number of bytes received recv_buffer = recv_buffer[:bytes_recvd] break self._msg_type, self._payload_remaining = self._next_data_header() request_size = min(self._payload_remaining, max_len - bytes_recvd) receive_exact_into(self._sync, view[:request_size]) self._payload_remaining -= request_size bytes_recvd += request_size view = view[request_size:] if bytes_recvd > max_len: raise MemoryError("scribbled past end of recv_buffer") # if there is no data remaining, set the RMT flag if self._payload_remaining == 0 and self._msg_type == "DataEnd": # # From IEEE Std 488.2: Response Message Terminator. # # RMT is the new-line accompanied by END sent from the server # to the client at the end of a response. Note that with HiSLIP # this is implied by the DataEND message. # self._rmt = 1 return recv_buffer def _next_data_header(self) -> Tuple[str, int]: """ receive the next data header (either Data or DataEnd), check the message_id, and return the msg_type and payload_length. """ while True: header = RxHeader(self._sync) if header.msg_type in ("Data", "DataEnd"): # When receiving Data messages if the MessageID is not 0xffff ffff, # then verify that the MessageID indicated in the Data message is # the MessageID that the client sent to the server with the most # recent Data, DataEND or Trigger message. # # If the MessageIDs do not match, the client shall clear any Data # responses already buffered and discard the offending Data message if ( header.message_parameter == 0xFFFF_FFFF or header.message_parameter == self.last_message_id ): break # we're out of sync. flush this message and continue. receive_flush(self._sync, header.payload_length) return header.msg_type, header.payload_length def device_clear(self) -> None: feature = self.async_device_clear() # Abandon pending messages and wait for in-process synchronous messages # to complete. time.sleep(0.1) # Indicate to server that synchronous channel is cleared out. self.device_clear_complete(feature) # reset messageID and resume normal opreation self._message_id = 0xFFFF_FF00 def initialize( self, version: tuple = (1, 0), vendor_id: bytes = b"xx", sub_address: bytes = b"hislip0", ) -> InitializeResponse: """ perform an Initialize transaction. returns the InitializeResponse header. """ major, minor = version header = struct.pack( "!2sBBBB2sQ", b"HS", MESSAGETYPE["Initialize"], 0, major, minor, vendor_id, len(sub_address), ) # txdecode(header, sub_address) # uncomment for debugging self._sync.sendall(header + sub_address) return InitializeResponse(self._sync) def async_initialize(self, session_id: int) -> AsyncInitializeResponse: """ perform an AsyncInitialize transaction. returns the AsyncInitializeResponse header. """ send_msg(self._async, "AsyncInitialize", 0, session_id) return AsyncInitializeResponse(self._async) def async_maximum_message_size(self, size: int) -> int: """ perform an AsyncMaxMsgSize transaction. returns the max_msg_size from the AsyncMaxMsgSizeResponse packet. """ # maximum_message_size transaction: # C->S: AsyncMaxMsgSize # S->C: AsyncMaxMsgSizeResponse payload = struct.pack("!Q", size) send_msg(self._async, "AsyncMaxMsgSize", 0, 0, payload) response = AsyncMaxMsgSizeResponse(self._async) return response.max_msg_size def async_lock_info(self) -> int: """ perform an AsyncLockInfo transaction. returns the exclusive_lock from the AsyncLockInfoResponse packet. """ # async_lock_info transaction: # C->S: AsyncLockInfo # S->C: AsyncLockInfoResponse send_msg(self._async, "AsyncLockInfo", 0, 0) response = AsyncLockInfoResponse(self._async) return response.exclusive_lock def async_lock_request(self, timeout: float, lock_string: str = "") -> str: """ perform an AsyncLock request transaction. returns the lock_response from the AsyncLockResponse packet. """ # async_lock transaction: # C->S: AsyncLock # S->C: AsyncLockResponse ctrl_code = LOCKCONTROLCODE["request"] timeout_ms = int(1e3 * timeout) send_msg(self._async, "AsyncLock", ctrl_code, timeout_ms, lock_string.encode()) response = AsyncLockResponse(self._async) return response.lock_response def async_lock_release(self) -> str: """ perform an AsyncLock release transaction. returns the lock_response from the AsyncLockResponse packet. """ # async_lock transaction: # C->S: AsyncLock # S->C: AsyncLockResponse ctrl_code = LOCKCONTROLCODE["release"] send_msg(self._async, "AsyncLock", ctrl_code, self.last_message_id) response = AsyncLockResponse(self._async) return response.lock_response def async_remote_local_control(self, remotelocalcontrol: str) -> None: """ perform an AsyncRemoteLocalControl transaction. """ # remote_local transaction: # C->S: AsyncRemoteLocalControl # S->C: AsyncRemoteLocalResponse ctrl_code = REMOTELOCALCONTROLCODE[remotelocalcontrol] send_msg( self._async, "AsyncRemoteLocalControl", ctrl_code, self.last_message_id ) AsyncRemoteLocalResponse(self._async) def async_status_query(self) -> int: """ perform an AsyncStatusQuery transaction. returns the server_status from the AsyncStatusResponse packet. """ # async_status_query transaction: # C->S: AsyncStatusQuery # S->C: AsyncStatusResponse send_msg(self._async, "AsyncStatusQuery", self._rmt, self._message_id) self._rmt = 0 response = AsyncStatusResponse(self._async) return response.server_status def async_device_clear(self) -> int: """ perform an AsyncDeviceClear transaction. returns the feature_bitmap from the AsyncDeviceClearAcknowledge packet. """ send_msg(self._async, "AsyncDeviceClear", 0, 0) response = AsyncDeviceClearAcknowledge(self._async) return response.feature_bitmap def device_clear_complete(self, feature_bitmap: int) -> int: """ perform a DeviceClear transaction. returns the feature_bitmap from the DeviceClearAcknowledge packet. """ send_msg(self._sync, "DeviceClearComplete", feature_bitmap, 0) response = DeviceClearAcknowledge(self._sync) return response.feature_bitmap def trigger(self) -> None: """send a Trigger packet on the sync channel""" send_msg(self._sync, "Trigger", self._rmt, self._message_id) self.last_message_id = self._message_id self._message_id = (self._message_id + 2) & 0xFFFF_FFFF def _send_data_packet(self, payload: bytes) -> None: """send a Data packet on the sync channel""" send_msg(self._sync, "Data", self._rmt, self._message_id, payload) self.last_message_id = self._message_id self._message_id = (self._message_id + 2) & 0xFFFF_FFFF def _send_data_end_packet(self, payload: bytes) -> None: """send a DataEnd packet on the sync channel""" send_msg(self._sync, "DataEnd", self._rmt, self._message_id, payload) self.last_message_id = self._message_id self._message_id = (self._message_id + 2) & 0xFFFF_FFFF def fatal_error(self, error: str, error_message: str = "") -> None: err_msg = (error_message or error).encode() send_msg(self._sync, "FatalError", FATALERRORCODE[error], 0, err_msg) def error(self, error: str, error_message: str = "") -> None: err_msg = (error_message or error).encode() send_msg(self._sync, "Error", ERRORCODE[error], 0, err_msg) # the following two routines are only used for debugging. # they are commented out because their f-strings use a feature # that is a syntax error in Python versions < 3.7 # def rxdecode(header): # ( # prologue, # msg_type, # control_code, # message_parameter, # payload_length, # ) = struct.unpack(HEADER_FORMAT, header) # # msg_type = MESSAGETYPE_STR[msg_type] # print( # f"Rx: {prologue=}, " # f"{msg_type=}, " # f"{control_code=}, " # f"{message_parameter=}, " # f"{payload_length=}" # ) # def txdecode(header, payload=b""): # ( # prologue, # msg_type, # control_code, # message_parameter, # payload_length, # ) = struct.unpack(HEADER_FORMAT, header) # # msg_type = MESSAGETYPE_STR[msg_type] # print( # f"Tx: {prologue=}, " # f"{msg_type=}, " # f"{control_code=}, " # f"{message_parameter=}, " # f"{payload_length=}, " # f"{len(payload)=}, " # f"{bytes(payload[:20]).decode('iso-8859-1')!r}" # ) pyvisa-py-0.7.2/pyvisa_py/protocols/rpc.py000066400000000000000000001014311457232645700207050ustar00rootroot00000000000000# -*- coding: utf-8 -*- """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! Original source: http://svn.python.org/projects/python/trunk/Demo/rpc/rpc.py :copyright: 2014-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ import enum import select import socket import struct import sys import time from ..common import logger from . import xdrlib #: 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 # N.B. Some devices may pad responses beyond RFC 1014 4-byte # alignment, so skip self.unpacker.done() call here which # would raise an exception in that case. See issue #225. return result def start_call(self, proc): # Don't override this self.lastxid += 1 cred = self.mkcred() verf = self.mkverf() p = self.packer p.reset() p.pack_callheader(self.lastxid, self.prog, self.vers, proc, cred, verf) p.proc = proc 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 _sendto(sock, data, address): """ loops calling sock.sendto() until all data is sent. """ ptr = 0 while data[ptr:]: ptr += sock.sendto(data[ptr:], address) # XXX sendfrag() should have been deleted when it was inlined into # _sendrecord() during refactoring def sendfrag(sock, last, frag): x = len(frag) if last: x = x | 0x80000000 header = struct.pack(">I", x) sock.sendall(header + frag) def _sendrecord(sock, record, fragsize=None, timeout=None): logger.debug("Sending record through %s: %r", sock, record) if timeout is not None: r, w, x = select.select([], [sock], [], timeout) if sock not in w: msg = "socket.timeout: The instrument seems to have stopped responding." raise socket.timeout(msg) last = False if not fragsize: fragsize = 0x7FFFFFFF while not last: record_len = len(record) if record_len <= fragsize: fragsize = record_len last = True if last: fragsize = fragsize | 0x80000000 header = struct.pack(">I", fragsize) sock.sendall(header + record[:fragsize]) record = record[fragsize:] def _recvrecord(sock, timeout, read_fun=None, min_packages=0): record = bytearray() buffer = bytearray() if not read_fun: read_fun = sock.recv wait_header = True last = False exp_length = 4 packages_received = 0 if min_packages != 0: logger.debug("Start receiving at least %i packages" % min_packages) # minimum is in interval 1 - 100ms based on timeout or for infinite it is # 1 sec min_select_timeout = ( max(min(timeout / 100.0, 0.1), 0.001) if timeout is not None else 1.0 ) # initial 'select_timeout' is half of timeout or max 2 secs # (max blocking time). # min is from 'min_select_timeout' select_timeout = ( max(min(timeout / 2.0, 2.0), min_select_timeout) if timeout is not None else 1.0 ) # time, when loop shall finish finish_time = time.time() + timeout if timeout is not None else 0 while True: # if more data for the current fragment is needed, use select # to wait for read ready, max `select_timeout` seconds if len(buffer) < exp_length: r, w, x = select.select([sock], [], [], select_timeout) read_data = b"" if sock in r: read_data = read_fun(exp_length) buffer.extend(read_data) logger.debug("received %r" % read_data) # Timeout was reached if not read_data: # no response or empty response if timeout is not None and time.time() >= finish_time: logger.debug( ( "Time out encountered in %s." "Already receieved %d bytes. Last fragment is %d " "bytes long and we were expecting %d" ), sock, len(record), len(buffer), exp_length, ) msg = ( "socket.timeout: The instrument seems to have stopped " "responding." ) raise socket.timeout(msg) elif min_packages != 0 and packages_received >= min_packages: logger.debug( "Stop receiving after %i of %i requested packages. Received record through %s: %r", packages_received, min_packages, sock, record, ) return bytes(record) else: # `select_timeout` decreased to 50% of previous or # min_select_timeout select_timeout = max(select_timeout / 2.0, min_select_timeout) continue if wait_header: # need to find header if len(buffer) >= exp_length: header = buffer[:exp_length] buffer = buffer[exp_length:] x = struct.unpack(">I", header)[0] last = (x & 0x80000000) != 0 exp_length = int(x & 0x7FFFFFFF) wait_header = False else: if len(buffer) >= exp_length: record.extend(buffer[:exp_length]) buffer = buffer[exp_length:] if last: logger.debug("Received record through %s: %r", sock, record) return bytes(record) else: wait_header = True exp_length = 4 packages_received += 1 def _connect(sock, host, port, timeout=0): try: sock.setblocking(0) sock.connect_ex((host, port)) except Exception: sock.close() return False finally: sock.setblocking(1) # minimum is in interval 100 - 500ms based on timeout min_select_timeout = max(min(timeout / 10.0, 0.5), 0.1) # initial 'select_timout' is half of timeout or max 2 secs # (max blocking time). # min is from 'min_select_timeout' select_timout = max(min(timeout / 2.0, 2.0), min_select_timeout) # time, when loop shall finish finish_time = time.time() + timeout while True: # use select to wait for socket ready, max `select_timout` seconds r, w, x = select.select([sock], [sock], [], select_timout) if sock in r or sock in w: return True if time.time() >= finish_time: # reached timeout sock.close() return False # `select_timout` decreased to 50% of previous or min_select_timeout select_timout = max(select_timout / 2.0, min_select_timeout) class RawTCPClient(Client): """Client using TCP to a specific port.""" def __init__(self, host, prog, vers, port, open_timeout=5000): Client.__init__(self, host, prog, vers, port) open_timeout = open_timeout if open_timeout is not None else 5000 self.connect(1e-3 * open_timeout) # self.timeout defaults higher than the default 2 second VISA timeout, # ensuring that VISA timeouts take precedence. self.timeout = 4.0 def make_call(self, proc, args, pack_func, unpack_func): """Overridden to allow for utilizing io_timeout (passed in args).""" if proc == 11: # vxi11.DEVICE_WRITE self.timeout = args[1] / 1000.0 elif proc in (12, 22): # vxi11.DEVICE_READ or vxi11.DEVICE_DOCMD self.timeout = args[2] / 1000.0 elif proc in (13, 14, 15, 16, 17): # vxi11.DEVICE_READSTB, vxi11.DEVICE_TRIGGER, vxi11.DEVICE_CLEAR, # vxi11.DEVICE_REMOTE, or vxi11.DEVICE_LOCAL self.timeout = args[3] / 1000.0 else: self.timeout = 4.0 # In case of a timeout because the instrument cannot answer, the # instrument should let use something went wrong. If we hit the hard # timeout of the rpc, it means something worse happened (cable # unplugged). self.timeout += 1.0 return super(RawTCPClient, self).make_call(proc, args, pack_func, unpack_func) def connect(self, timeout=5.0): logger.debug( "RawTCPClient: connecting to socket at (%s, %s)", self.host, self.port ) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if not _connect(self.sock, self.host, self.port, timeout): raise RPCError("can't connect to server") def close(self): logger.debug("RawTCPClient: closing socket") self.sock.close() def do_call(self): call = self.packer.get_buf() _sendrecord(self.sock, call, timeout=self.timeout) try: min_packages = int(self.packer.proc == 3) logger.debug("RawTCPClient: procedure type %i" % self.packer.proc) # if the command is get_port, we only expect one package. # This is a workaround for misbehaving instruments. except AttributeError: min_packages = 0 while True: reply = _recvrecord(self.sock, self.timeout, min_packages=min_packages) u = self.unpacker u.reset(reply) xid, verf = u.unpack_replyheader() if xid == self.lastxid: # xid matches, we're done return elif xid < self.lastxid: # Stale data in buffer due to interruption # Discard and fetch another record continue else: # xid larger than expected - packet from the future? raise RPCError( "wrong xid in reply %r instead of %r" % (xid, self.lastxid) ) class RawUDPClient(Client): """Client using UDP to a specific port.""" def __init__(self, host, prog, vers, port): Client.__init__(self, host, prog, vers, port) self.connect() def connect(self): logger.debug( "RawTCPClient: connecting to socket at (%s, %s)", self.host, self.port ) 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.sendall(call) BUFSIZE = 8192 # Max UDP buffer size timeout = 1 count = 5 while 1: r, w, x = [self.sock], [], [] if select: r, w, x = select.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.sendall(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() _sendto(self.sock, call, (self.host, self.port)) 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.select(r, w, x) else: r, w, x = select.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 def send_call(self, proc, args, pack_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() try: _sendto(self.sock, call, (self.host, self.port)) except OSError as exc: raise RPCError("unable to send broadcast") from exc def recv_call(self, unpack_func): 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.select(r, w, x) else: r, w, x = select.select(r, w, x, self.timeout) if self.sock not in r: break try: reply, fromaddr = self.sock.recvfrom(BUFSIZE) except OSError as exc: raise RPCError("unable to recieve broadcast") from exc 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 send_port(self, mapping): return self.send_call( PortMapperVersion.get_port, mapping, self.packer.pack_mapping, ) def recv_port(self, mapping): return self.recv_call( 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, open_timeout=5000): RawTCPClient.__init__(self, host, PMAP_PROG, PMAP_VERS, PMAP_PORT, open_timeout) 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, open_timeout=5000): pmap = TCPPortMapperClient(host, open_timeout) port = pmap.get_port((prog, vers, IPPROTO_TCP, 0)) pmap.close() if port == 0: raise RPCError("program not registered") RawTCPClient.__init__(self, host, prog, vers, port, open_timeout) 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() _ = 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() # noqa verf = self.unpacker.unpack_auth() # noqa 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, None) 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: _sendto(self.sock, reply, host_port) pyvisa-py-0.7.2/pyvisa_py/protocols/usbraw.py000066400000000000000000000044251457232645700214310ustar00rootroot00000000000000# -*- coding: utf-8 -*- """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-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ from .usbtmc import USBRaw as USBRaw from .usbutil import find_devices, find_interfaces 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 received = bytearray() while not len(received) >= size: resp = raw_read(self.RECV_CHUNK) received.extend(resp) return bytes(received) pyvisa-py-0.7.2/pyvisa_py/protocols/usbtmc.py000066400000000000000000000350421457232645700214220ustar00rootroot00000000000000# -*- coding: utf-8 -*- """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-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ import enum import struct import time import warnings from collections import namedtuple import usb from .usbutil import find_devices, find_endpoint, find_interfaces, usb_find_desc 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 # USB488 trigger = 128 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 # USB488 read_status_byte = 128 ren_control = 160 go_to_local = 161 local_lockout = 162 class UsbTmcStatus(enum.IntEnum): success = 1 pending = 2 failed = 0x80 transfer_not_in_progress = 0x81 split_not_in_progress = 0x82 split_in_progress = 0x83 UsbTmcCapabilities = namedtuple("UsbTmcCapabilities", "usb488 ren_control trigger") 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( f"{len(devices)} devices found:\n{desc}\nPlease narrow the search" " criteria" ) self.usb_dev = devices[0] try: if self.usb_dev.is_kernel_driver_active(0): self.usb_dev.detach_kernel_driver(0) except (usb.core.USBError, NotImplementedError): pass try: cfg = self.usb_dev.get_active_configuration() except usb.core.USBError: cfg = None if cfg is None: try: self.usb_dev.set_configuration() cfg = self.usb_dev.get_active_configuration() except usb.core.USBError as e: raise Exception("failed to set configuration\n %s" % e) intf = cfg[(0, 0)] # Check if the interface exposes multiple alternative setting and # set one only if there is more than one. if ( len( tuple( usb.util.find_descriptor( cfg, find_all=True, bInterfaceNumber=intf.bInterfaceNumber ) ) ) > 1 ): try: self.usb_dev.set_interface_altsetting() except usb.core.USBError: 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 = self.usb_recv_ep.read(size, self.timeout).tobytes() return data def close(self): return usb.util.dispose_resources(self.usb_dev) class USBTMC(USBRaw): 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 ) time.sleep(0.01) self._capabilities = self._get_capabilities() self._btag = 0 if not (self.usb_recv_ep and self.usb_send_ep): msg = "TMC device must have both Bulk-In and Bulk-out endpoints." raise ValueError(msg) self._enable_remote_control() def _enable_remote_control(self): if not self._capabilities.ren_control: return 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.ren_control, 1, self.usb_intf.index, 1, timeout=self.timeout, ) def _get_capabilities(self): c = 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, ) usb488_capabilities = c[0xE] # bit #2: The interface is a 488.2 USB488 interface. # bit #1: The interface accepts REN_CONTROL, GO_TO_LOCAL, # and LOCAL_LOCKOUT requests. # bit #0: The interface accepts the MsgID = TRIGGER # USBTMC command message and forwards # TRIGGER requests to the Function Layer. return UsbTmcCapabilities( usb488=bool(usb488_capabilities & (1 << 2)), ren_control=bool(usb488_capabilities & (1 << 1)), trigger=bool(usb488_capabilities & (1 << 0)), ) 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 _abort_bulk_in(self, btag): """Request that the device abort a pending Bulk-IN operation.""" abort_timeout_ms = 5000 # Send INITIATE_ABORT_BULK_IN. # According to USBTMC 1.00 4.2.1.4: # wValue = bTag value of transfer to be aborted # wIndex = Bulk-IN endpoint # wLength = 0x0002 (length of device response) data = self.usb_dev.ctrl_transfer( usb.util.build_request_type( usb.util.CTRL_IN, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_ENDPOINT, ), Request.initiate_abort_bulk_in, btag, self.usb_recv_ep.bEndpointAddress, 0x0002, timeout=abort_timeout_ms, ) if data[0] != UsbTmcStatus.success: # Abort Bulk-IN failed. Ignore it. return # Read remaining data from Bulk-IN endpoint. self.usb_recv_ep.read(self.usb_recv_ep.wMaxPacketSize, abort_timeout_ms) # Send CHECK_ABORT_BULK_IN_STATUS until it completes. # According to USBTMC 1.00 4.2.1.5: # wValue = 0x0000 # wIndex = Bulk-IN endpoint # wLength = 0x0008 (length of device response) for retry in range(100): data = self.usb_dev.ctrl_transfer( usb.util.build_request_type( usb.util.CTRL_IN, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_ENDPOINT, ), Request.check_abort_bulk_in_status, 0x0000, self.usb_recv_ep.bEndpointAddress, 0x0008, timeout=abort_timeout_ms, ) if data[0] != UsbTmcStatus.pending: break time.sleep(0.05) 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 # Send all data via one or more Bulk-OUT transfers. # Set the EOM flag on the last transfer only. # Send at least one transfer (possibly empty). while (end == 0) or (end < size): begin, end = end, begin + self.usb_send_ep.wMaxPacketSize self._btag = (self._btag % 255) + 1 eom = end >= size data = BulkOutMessage.build_array(self._btag, eom, data[begin:end]) bytes_sent += raw_write(data) return size def read(self, size): header_size = 12 max_padding = 511 recv_chunk = self.usb_recv_ep.wMaxPacketSize - header_size if size > 0 and size < recv_chunk: recv_chunk = size eom = False raw_read = super(USBTMC, self).read raw_write = super(USBTMC, self).write received = bytearray() while not eom: self._btag = (self._btag % 255) + 1 req = BulkInMessage.build_array(self._btag, recv_chunk, None) raw_write(req) try: resp = raw_read(recv_chunk + header_size + max_padding) response = BulkInMessage.from_bytes(resp) except (usb.core.USBError, ValueError): # Abort failed Bulk-IN operation. self._abort_bulk_in(self._btag) raise received.extend(response.data) # Detect EOM only when device sends all expected bytes. if len(response.data) >= response.transfer_size: eom = response.transfer_attributes & 1 return bytes(received) pyvisa-py-0.7.2/pyvisa_py/protocols/usbutil.py000066400000000000000000000223001457232645700216050ustar00rootroot00000000000000# -*- coding: utf-8 -*- """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-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ from fnmatch import fnmatch import usb from usb.util import 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", } 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(): try: value = getattr(dev, attr) except (NotImplementedError, ValueError): return False if not fnmatch(value.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 Exception: pass except Exception: 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 pyvisa-py-0.7.2/pyvisa_py/protocols/vxi11.py000066400000000000000000000244231457232645700210760ustar00rootroot00000000000000# -*- coding: utf-8 -*- """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-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ import enum import socket from . import rpc # fmt: off # 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 # fmt: on 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, open_timeout=5000): self.packer = Vxi11Packer() self.unpacker = Vxi11Unpacker("") super(CoreClient, self).__init__( host, DEVICE_CORE_PROG, DEVICE_CORE_VERS, open_timeout ) def create_link(self, id, lock_device, lock_timeout, name): params = (id, lock_device, lock_timeout, name) try: return self.make_call( CREATE_LINK, params, self.packer.pack_create_link_parms, self.unpacker.unpack_create_link_resp, ) except socket.timeout: return ErrorCodes.device_not_accessible, None, None, None def device_write(self, link, io_timeout, lock_timeout, flags, data): params = (link, io_timeout, lock_timeout, flags, data) try: return self.make_call( DEVICE_WRITE, params, self.packer.pack_device_write_parms, self.unpacker.unpack_device_write_resp, ) except socket.timeout as e: return ErrorCodes.io_error, e.args[0] def device_read( self, link, request_size, io_timeout, lock_timeout, flags, term_char ): params = (link, request_size, io_timeout, lock_timeout, flags, term_char) try: return self.make_call( DEVICE_READ, params, self.packer.pack_device_read_parms, self.unpacker.unpack_device_read_resp, ) except socket.timeout as e: return ErrorCodes.io_error, e.args[0], "" 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 ) pyvisa-py-0.7.2/pyvisa_py/protocols/xdrlib.py000066400000000000000000000137021457232645700214100ustar00rootroot00000000000000# Taken from Python 3.12 and vendored since xdrlib will be removed in 3.13 # Warning about deprecation was removed and code reformatted using black """Implements (a subset of) Sun XDR -- eXternal Data Representation. See: RFC 1014 """ import struct from functools import wraps from io import BytesIO __all__ = ["Error", "Packer", "Unpacker", "ConversionError"] # exceptions class Error(Exception): """Exception class for this module. Use: except xdrlib.Error as var: # var has the Error instance for the exception Public ivars: msg -- contains the message """ def __init__(self, msg): self.msg = msg def __repr__(self): return repr(self.msg) def __str__(self): return str(self.msg) class ConversionError(Error): pass def raise_conversion_error(function): """Wrap any raised struct.errors in a ConversionError.""" @wraps(function) def result(self, value): try: return function(self, value) except struct.error as e: raise ConversionError(e.args[0]) from None return result class Packer: """Pack various data representations into a buffer.""" def __init__(self): self.reset() def reset(self): self.__buf = BytesIO() def get_buffer(self): return self.__buf.getvalue() # backwards compatibility get_buf = get_buffer @raise_conversion_error def pack_uint(self, x): self.__buf.write(struct.pack(">L", x)) @raise_conversion_error def pack_int(self, x): self.__buf.write(struct.pack(">l", x)) pack_enum = pack_int def pack_bool(self, x): if x: self.__buf.write(b"\0\0\0\1") else: self.__buf.write(b"\0\0\0\0") def pack_uhyper(self, x): try: self.pack_uint(x >> 32 & 0xFFFFFFFF) except (TypeError, struct.error) as e: raise ConversionError(e.args[0]) from None try: self.pack_uint(x & 0xFFFFFFFF) except (TypeError, struct.error) as e: raise ConversionError(e.args[0]) from None pack_hyper = pack_uhyper @raise_conversion_error def pack_float(self, x): self.__buf.write(struct.pack(">f", x)) @raise_conversion_error def pack_double(self, x): self.__buf.write(struct.pack(">d", x)) def pack_fstring(self, n, s): if n < 0: raise ValueError("fstring size must be nonnegative") data = s[:n] n = ((n + 3) // 4) * 4 data = data + (n - len(data)) * b"\0" self.__buf.write(data) pack_fopaque = pack_fstring def pack_string(self, s): n = len(s) self.pack_uint(n) self.pack_fstring(n, s) pack_opaque = pack_string pack_bytes = pack_string def pack_list(self, list, pack_item): for item in list: self.pack_uint(1) pack_item(item) self.pack_uint(0) def pack_farray(self, n, list, pack_item): if len(list) != n: raise ValueError("wrong array size") for item in list: pack_item(item) def pack_array(self, list, pack_item): n = len(list) self.pack_uint(n) self.pack_farray(n, list, pack_item) class Unpacker: """Unpacks various data representations from the given buffer.""" def __init__(self, data): self.reset(data) def reset(self, data): self.__buf = data self.__pos = 0 def get_position(self): return self.__pos def set_position(self, position): self.__pos = position def get_buffer(self): return self.__buf def done(self): if self.__pos < len(self.__buf): raise Error("unextracted data remains") def unpack_uint(self): i = self.__pos self.__pos = j = i + 4 data = self.__buf[i:j] if len(data) < 4: raise EOFError return struct.unpack(">L", data)[0] def unpack_int(self): i = self.__pos self.__pos = j = i + 4 data = self.__buf[i:j] if len(data) < 4: raise EOFError return struct.unpack(">l", data)[0] unpack_enum = unpack_int def unpack_bool(self): return bool(self.unpack_int()) def unpack_uhyper(self): hi = self.unpack_uint() lo = self.unpack_uint() return int(hi) << 32 | lo def unpack_hyper(self): x = self.unpack_uhyper() if x >= 0x8000000000000000: x = x - 0x10000000000000000 return x def unpack_float(self): i = self.__pos self.__pos = j = i + 4 data = self.__buf[i:j] if len(data) < 4: raise EOFError return struct.unpack(">f", data)[0] def unpack_double(self): i = self.__pos self.__pos = j = i + 8 data = self.__buf[i:j] if len(data) < 8: raise EOFError return struct.unpack(">d", data)[0] def unpack_fstring(self, n): if n < 0: raise ValueError("fstring size must be nonnegative") i = self.__pos j = i + (n + 3) // 4 * 4 if j > len(self.__buf): raise EOFError self.__pos = j return self.__buf[i : i + n] unpack_fopaque = unpack_fstring def unpack_string(self): n = self.unpack_uint() return self.unpack_fstring(n) unpack_opaque = unpack_string unpack_bytes = unpack_string def unpack_list(self, unpack_item): list = [] while (x := self.unpack_uint()) != 0: if x != 1: raise ConversionError("0 or 1 expected, got %r" % (x,)) item = unpack_item() list.append(item) return list def unpack_farray(self, n, unpack_item): list = [] for i in range(n): list.append(unpack_item()) return list def unpack_array(self, unpack_item): n = self.unpack_uint() return self.unpack_farray(n, unpack_item) pyvisa-py-0.7.2/pyvisa_py/serial.py000066400000000000000000000366621457232645700173710ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Serial Session implementation using PySerial. :copyright: 2014-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ import sys from typing import Any, List, Tuple from pyvisa import attributes, constants, logger, rname from pyvisa.constants import ( BufferOperation, ResourceAttribute, SerialTermination, StatusCode, ) from . import common from .sessions import Session, UnknownAttribute try: import serial from serial.tools.list_ports import comports except ImportError as e: Session.register_unavailable( constants.InterfaceType.asrl, "INSTR", "Please install PySerial (>=3.0) to use this resource type.\n%s" % e, ) raise IS_WIN = sys.platform == "win32" def to_state(boolean_input: bool) -> constants.LineState: """Convert a boolean input into a LineState value.""" if boolean_input: return constants.LineState.asserted return constants.LineState.unasserted @Session.register(constants.InterfaceType.asrl, "INSTR") class SerialSession(Session): """A serial Session that uses PySerial to do the low level communication.""" # Override parsed to take into account the fact that this class is only used # for a specific kind of resource parsed: rname.ASRLInstr @staticmethod def list_resources() -> List[str]: return [ "ASRL%s::INSTR" % (port[0][3:] if IS_WIN else port[0]) for port in comports() ] @classmethod def get_low_level_info(cls) -> str: try: ver = serial.VERSION except AttributeError: ver = "N/A" return "via PySerial (%s)" % ver def after_parsing(self) -> None: self.interface = serial.serial_for_url( ("COM" if IS_WIN else "") + self.parsed.board, timeout=self.timeout, write_timeout=self.timeout, ) for name in ( "ASRL_END_IN", "ASRL_END_OUT", "SEND_END_EN", "TERMCHAR", "TERMCHAR_EN", "SUPPRESS_END_EN", ): attribute = getattr(constants, "VI_ATTR_" + name) self.attrs[attribute] = attributes.AttributesByID[attribute].default def _get_timeout(self, attribute: ResourceAttribute) -> Tuple[int, StatusCode]: if self.interface: self.timeout = self.interface.timeout return super(SerialSession, self)._get_timeout(attribute) def _set_timeout(self, attribute: ResourceAttribute, value: int) -> StatusCode: status = super(SerialSession, self)._set_timeout(attribute, value) if self.interface: self.interface.timeout = self.timeout self.interface.write_timeout = self.timeout return status def close(self) -> StatusCode: self.interface.close() return StatusCode.success def read(self, count: int) -> Tuple[bytes, StatusCode]: """Reads data from device or interface synchronously. Corresponds to viRead function of the VISA library. Parameters ----------- count : int Number of bytes to be read. Returns ------- bytes Data read from the device StatusCode Return value of the library call. """ end_in, _ = self.get_attribute(ResourceAttribute.asrl_end_in) suppress_end_en, _ = self.get_attribute(ResourceAttribute.suppress_end_enabled) reader = lambda: self.interface.read(1) # noqa: E731 if end_in == SerialTermination.none: checker = lambda current: False # noqa: E731 elif end_in == SerialTermination.last_bit: mask = 2**self.interface.bytesize checker = lambda current: bool(current[-1] & mask) # noqa: E731 elif end_in == SerialTermination.termination_char: end_char, _ = self.get_attribute(ResourceAttribute.termchar) checker = lambda current: current[-1] == end_char # noqa: E731 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: bytes) -> Tuple[int, StatusCode]: """Writes data to device or interface synchronously. Corresponds to viWrite function of the VISA library. Parameters ---------- data : bytes Data to be written. Returns ------- int Number of bytes actually transferred StatusCode Return value of the library call. """ logger.debug("Serial.write %r" % data) send_end, _ = self.get_attribute(ResourceAttribute.send_end_enabled) end_out, _ = self.get_attribute(ResourceAttribute.asrl_end_out) data_bits, _ = self.get_attribute(constants.ResourceAttribute.asrl_data_bits) if end_out == SerialTermination.none: pass elif end_out == SerialTermination.last_bit: data = b"".join(common.iter_bytes(data, data_bits, send_end)) elif end_out == SerialTermination.termination_char: term_char, _ = self.get_attribute(ResourceAttribute.termchar) data = b"".join(common.iter_bytes(data, data_bits, send_end=None)) data = data + common.int_to_byte(term_char) elif end_out == SerialTermination.termination_break: data = b"".join(common.iter_bytes(data, data_bits, send_end=None)) else: raise ValueError("Unknown value for VI_ATTR_ASRL_END_OUT: %s" % end_out) try: count = self.interface.write(data) if end_out == SerialTermination.termination_break: logger.debug("Serial.sendBreak") self.interface.sendBreak() return count, StatusCode.success except serial.SerialTimeoutException: return 0, StatusCode.error_timeout def flush(self, mask: BufferOperation) -> StatusCode: """Flush the specified buffers. The buffers can be associated with formatted I/O operations and/or serial communication. Corresponds to viFlush function of the VISA library. Parameters ---------- mask : constants.BufferOperation Specifies the action to be taken with flushing the buffer. The values can be combined using the | operator. However multiple operations on a single buffer cannot be combined. Returns ------- constants.StatusCode Return value of the library call. """ if mask & BufferOperation.discard_read_buffer: self.interface.reset_input_buffer() if ( mask & BufferOperation.flush_write_buffer or mask & BufferOperation.flush_transmit_buffer ): self.interface.flush() if ( mask & BufferOperation.discard_write_buffer or mask & BufferOperation.discard_transmit_buffer ): self.interface.reset_output_buffer() return StatusCode.success def _get_attribute( # noqa: C901 self, attribute: constants.ResourceAttribute ) -> Tuple[Any, StatusCode]: """Get the value for a given VISA attribute for this session. Use to implement custom logic for attributes. Parameters ---------- attribute : ResourceAttribute Attribute for which the state query is made Returns ------- Any State of the queried attribute for a specified resource StatusCode Return value of the library call. """ if attribute == constants.VI_ATTR_ASRL_ALLOW_TRANSMIT: raise NotImplementedError elif attribute == constants.VI_ATTR_ASRL_AVAIL_NUM: return self.interface.inWaiting(), StatusCode.success elif attribute == constants.VI_ATTR_ASRL_BAUD: return self.interface.baudrate, 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_CTS_STATE: return to_state(self.interface.getCTS()), StatusCode.success elif attribute == constants.VI_ATTR_ASRL_DATA_BITS: return self.interface.bytesize, 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()), StatusCode.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 ), StatusCode.success, ) elif attribute == constants.VI_ATTR_ASRL_PARITY: parity = self.interface.parity if parity == serial.PARITY_NONE: return constants.Parity.none, StatusCode.success elif parity == serial.PARITY_EVEN: return constants.Parity.even, StatusCode.success elif parity == serial.PARITY_ODD: return constants.Parity.odd, StatusCode.success elif parity == serial.PARITY_MARK: return constants.Parity.mark, StatusCode.success elif parity == serial.PARITY_SPACE: return constants.Parity.space, StatusCode.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, StatusCode.success elif bits == serial.STOPBITS_ONE_POINT_FIVE: return constants.StopBits.one_and_a_half, StatusCode.success elif bits == serial.STOPBITS_TWO: return constants.StopBits.two, StatusCode.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, StatusCode.success raise UnknownAttribute(attribute) def _set_attribute( # noqa: C901 self, attribute: constants.ResourceAttribute, attribute_state: Any ) -> StatusCode: """Sets the state of an attribute. Corresponds to viSetAttribute function of the VISA library. Parameters ---------- attribute : constants.ResourceAttribute Attribute for which the state is to be modified. (Attributes.*) attribute_state : Any The state of the attribute to be set for the specified object. Returns ------- StatusCode Return value of the library call. """ 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: raise NotImplementedError 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 Exception: 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: if attribute_state == constants.StopBits.one: self.interface.stopbits = serial.STOPBITS_ONE return StatusCode.success if attribute_state == constants.StopBits.one_and_a_half: self.interface.stopbits = serial.STOPBITS_ONE_POINT_FIVE return StatusCode.success if attribute_state == constants.StopBits.two: self.interface.stopbits = serial.STOPBITS_TWO return StatusCode.success return StatusCode.error_nonsupported_attribute_state elif attribute == constants.VI_ATTR_ASRL_XOFF_CHAR: raise NotImplementedError raise UnknownAttribute(attribute) pyvisa-py-0.7.2/pyvisa_py/sessions.py000066400000000000000000000672141457232645700177550ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Base Session class. :copyright: 2014-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ import abc import time from typing import ( Any, Callable, ClassVar, Dict, Iterator, List, Optional, Tuple, Type, TypeVar, ) from pyvisa import attributes, constants, logger, rname from pyvisa.constants import ResourceAttribute, StatusCode from pyvisa.typing import VISARMSession from .common import int_to_byte #: Type var used when typing register. T = TypeVar("T", bound=Type["Session"]) class UnknownAttribute(Exception): """Custom exception signaling a VISA attribute is not supported.""" def __init__(self, attribute: constants.ResourceAttribute) -> None: self.attribute = attribute def __str__(self) -> str: 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(metaclass=abc.ABCMeta): """A base class for Session objects. Just makes sure that common methods are defined and information is stored. Parameters ---------- resource_manager_session : VISARMSession Session handle of the parent Resource Manager resource_name : str Name of the resource this session is communicating with parsed : rname.ResourceName, optional Parsed representation of the resource name. The default is False meaning that the provided resource name will be parsed. """ @abc.abstractmethod def _get_attribute( self, attribute: constants.ResourceAttribute ) -> Tuple[Any, StatusCode]: """Get the value for a given VISA attribute for this session. Use to implement custom logic for attributes. Parameters ---------- attribute : constants.ResourceAttribute Resource attribute for which the state query is made Returns ------- Any State of the queried attribute for a specified resource constants.StatusCode Return value of the library call. """ @abc.abstractmethod def _set_attribute( self, attribute: constants.ResourceAttribute, attribute_state: Any ) -> StatusCode: """Set the attribute_state value for a given VISA attribute for this session. Use to implement custom logic for attributes. Parameters ---------- attribute : constants.ResourceAttribute Resource attribute for which the state query is made. attribute_state : Any Value to which to set the attribute. Returns ------- StatusCode The return value of the library call. """ @abc.abstractmethod def close(self) -> StatusCode: """Close the session. Use it to do final clean ups. """ #: Session handle of the parent Resource Manager resource_manager_session: VISARMSession #: Name of the resource this session is communicating with resource_name: str #: Parsed representation of the resource name. parsed: rname.ResourceName #: Session type as (Interface Type, Resource Class) session_type: Tuple[constants.InterfaceType, str] #: Timeout in milliseconds to use when opening the resource. open_timeout: Optional[int] #: Value of the timeout in seconds used for general operation timeout: Optional[float] #: Used as a place holder for the object doing the lowlevel communication. interface: Any #: Used for attributes not handled by the underlying interface. #: Values are get or set automatically by get_attribute and set_attribute attrs: Dict[constants.ResourceAttribute, Any] #: Maps (Interface Type, Resource Class) to Python class encapsulating that #: resource. #: dict[(Interface Type, Resource Class) , Session] _session_classes: ClassVar[ Dict[Tuple[constants.InterfaceType, str], Type["Session"]] ] = {} @staticmethod def list_resources() -> List[str]: """List the resources available for the resource class.""" return [] @classmethod def get_low_level_info(cls) -> str: """Get info about the backend used by the session.""" return "" @classmethod def iter_valid_session_classes( cls, ) -> Iterator[Tuple[Tuple[constants.InterfaceType, str], Type["Session"]]]: """Iterator over valid sessions classes infos.""" for key, val in cls._session_classes.items(): if not issubclass(val, UnavailableSession): yield key, val @classmethod def iter_session_classes_issues( cls, ) -> Iterator[Tuple[Tuple[constants.InterfaceType, str], str]]: """Iterator over invalid sessions classes (i.e. those with import errors).""" for key, val in cls._session_classes.items(): if issubclass(val, UnavailableSession): yield key, getattr(val, "session_issue") @classmethod def get_session_class( cls, interface_type: constants.InterfaceType, resource_class: str ) -> Type["Session"]: """Get the session class for a given interface type and resource class. Parameters ---------- interface_type : constants.InterfaceType Type of interface. resource_class : str Class of resource. Returns ------- Sessions Session subclass the most appropriate for the resource. """ 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: constants.InterfaceType, resource_class: str ) -> Callable[[T], T]: """Register a session class for a given interface type and resource class. Parameters ---------- interface_type : constants.InterfaceType Type of interface. resource_class : str Class of the resource Returns ------- Callable[[T], T] Decorator function to register a session subclass. """ 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: constants.InterfaceType, resource_class: str, msg: str ) -> None: """Register that no session class exists. This creates a fake session that will raise a ValueError if called. Parameters ---------- interface_type : constants.InterfaceType Type of interface. resource_class : str Class of the resource msg : str Message detailing why no session class exists for this particular interface type, resource class pair. Returns ------- Type[Session] Fake session. """ class _internal(UnavailableSession): #: Message detailing why no session is available. 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: VISARMSession, resource_name: str, parsed: Optional[rname.ResourceName] = None, open_timeout: Optional[int] = None, ) -> None: if parsed is None: parsed = rname.parse_resource_name(resource_name) self.parsed = parsed self.open_timeout = open_timeout #: Used as a place holder for the object doing the lowlevel communication. 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 = { ResourceAttribute.resource_manager_session: resource_manager_session, ResourceAttribute.resource_name: str(parsed), ResourceAttribute.resource_class: parsed.resource_class, ResourceAttribute.interface_type: parsed.interface_type_const, ResourceAttribute.timeout_value: (self._get_timeout, self._set_timeout), } #: Timeout expressed in second or None for the absence of a timeout. #: The default value is set when calling self.set_attribute(attr, default_timeout) self.timeout = None #: Set the default timeout from constants attr = ResourceAttribute.timeout_value default_timeout = attributes.AttributesByID[attr].default self.set_attribute(attr, default_timeout) self.after_parsing() def after_parsing(self) -> None: """Override this method to provide custom initialization code, to be called after the resource name is properly parsed ResourceSession can register resource specific attributes handling of them into self.attrs. It is also possible to change handling of already registered common attributes. List of attributes is available in pyvisa package: * name is in constants module as: VI_ATTR_ * validity of attribute for resource is defined module attributes, AttrVI_ATTR_.resources For static (read only) values, simple readonly and also readwrite attributes simplified construction can be used: ` self.attrs[constants.VI_ATTR_] = 100` or ` self.attrs[constants.VI_ATTR_] = ` For more complex handling of attributes, it is possible to register getter and/or setter. When Null is used, NotSupported error is returned. Getter has same signature as see Session._get_attribute and setter has same signature as see Session._set_attribute. (It is possible to register also see Session._get_attribute and see Session._set_attribute as getter/setter). Getter and Setter are registered as tuple. For readwrite attribute: ` self.attrs[constants.VI_ATTR_] = (, )` For readonly attribute: ` self.attrs[constants.VI_ATTR_] = (, None)` For reusing of see Session._get_attribute and see Session._set_attribute ` self.attrs[constants.VI_ATTR_] = (self._get_attribute, self._set_attribute)` """ pass def write(self, data: bytes) -> Tuple[int, StatusCode]: """Writes data to device or interface synchronously. Corresponds to viWrite function of the VISA library. Parameters ---------- data : bytes Data to be written. Returns ------- int Number of bytes actually transferred StatusCode Return value of the library call. """ raise NotImplementedError def read(self, count: int) -> Tuple[bytes, StatusCode]: """Reads data from device or interface synchronously. Corresponds to viRead function of the VISA library. Parameters ----------- count : int Number of bytes to be read. Returns ------- bytes Data read from the device StatusCode Return value of the library call. """ raise NotImplementedError() def clear(self) -> StatusCode: """Clears a device. Corresponds to viClear function of the VISA library. Returns ------- StatusCode Return value of the library call. """ return StatusCode.error_nonsupported_operation def flush(self, mask: constants.BufferOperation) -> StatusCode: """Flush the specified buffers. The buffers can be associated with formatted I/O operations and/or serial communication. Corresponds to viFlush function of the VISA library. Parameters ---------- mask : constants.BufferOperation Specifies the action to be taken with flushing the buffer. The values can be combined using the | operator. However multiple operations on a single buffer cannot be combined. Returns ------- constants.StatusCode Return value of the library call. """ raise NotImplementedError def read_stb(self) -> Tuple[int, StatusCode]: """Reads a status byte of the service request. Corresponds to viReadSTB function of the VISA library. Returns ------- int Service request status byte StatusCode Return value of the library call. """ return 0, StatusCode.error_nonsupported_operation def lock( self, lock_type: constants.Lock, timeout: int, requested_key: Optional[str] = None, ): """Establishes an access mode to the specified resources. Corresponds to viLock function of the VISA library. Parameters ---------- lock_type : constants.Lock Specifies the type of lock requested. timeout : int Absolute time period (in milliseconds) that a resource waits to get unlocked by the locking session before returning an error. requested_key : Optional[str], optional Requested locking key in the case of a shared lock. For an exclusive lock it should be None. Returns ------- Optional[str] Key that can then be passed to other sessions to share the lock, or None for an exclusive lock. constants.StatusCode Return value of the library call. """ return "", StatusCode.error_nonsupported_operation def unlock(self) -> StatusCode: """Relinquishes a lock for the specified resource. Corresponds to viUnlock function of the VISA library. Returns ------- StatusCode Return value of the library call. """ return StatusCode.error_nonsupported_operation def gpib_command(self, command_byte: bytes) -> Tuple[int, StatusCode]: """Write GPIB command bytes on the bus. Corresponds to viGpibCommand function of the VISA library. Parameters ---------- data : bytes Data to write. Returns ------- int Number of written bytes constants.StatusCode Return value of the library call. """ return 0, StatusCode.error_nonsupported_operation def assert_trigger(self, protocol: constants.TriggerProtocol) -> StatusCode: """Assert software or hardware trigger. Corresponds to viAssertTrigger function of the VISA library. Parameters ---------- protocol : constants.TriggerProtocol Trigger protocol to use during assertion. Returns ------- constants.StatusCode Return value of the library call. """ raise NotImplementedError def gpib_send_ifc(self) -> StatusCode: """Pulse the interface clear line (IFC) for at least 100 microseconds. Corresponds to viGpibSendIFC function of the VISA library. Returns ------- constants.StatusCode Return value of the library call. """ return StatusCode.error_nonsupported_operation def gpib_control_ren( self, mode: constants.RENLineOperation ) -> constants.StatusCode: """Controls the state of the GPIB Remote Enable (REN) interface line. Optionally the remote/local state of the device can also be set. Corresponds to viGpibControlREN function of the VISA library. Parameters ---------- mode : constants.RENLineOperation State of the REN line and optionally the device remote/local state. Returns ------- constants.StatusCode Return value of the library call. """ return StatusCode.error_nonsupported_operation def gpib_control_atn(self, mode: constants.ATNLineOperation) -> StatusCode: """Specifies the state of the ATN line and the local active controller state. Corresponds to viGpibControlATN function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. mode : constants.ATNLineOperation State of the ATN line and optionally the local active controller state. Returns ------- constants.StatusCode Return value of the library call. """ return StatusCode.error_nonsupported_operation def gpib_pass_control( self, primary_address: int, secondary_address: int ) -> StatusCode: """Tell a GPIB device to become controller in charge (CIC). Corresponds to viGpibPassControl function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. primary_address : int Primary address of the GPIB device to which you want to pass control. secondary_address : int Secondary address of the targeted GPIB device. If the targeted device does not have a secondary address, this parameter should contain the value Constants.VI_NO_SEC_ADDR. Returns ------- constants.StatusCode Return value of the library call. """ return StatusCode.error_nonsupported_operation def get_attribute(self, attribute: ResourceAttribute) -> Tuple[Any, StatusCode]: """Get the value for a given VISA attribute for this session. Does a few checks before and calls before dispatching to `_get_attribute`. Parameters ---------- attribute : ResourceAttribute Resource attribute for which the state query is made Returns ------- Any The state of the queried attribute for a specified resource. StatusCode Return value of the library call. """ # Check if the attribute value is defined. try: attr = attributes.AttributesByID[attribute] except KeyError: return 0, StatusCode.error_nonsupported_attribute # Check if the attribute is defined for this session type. if not attr.in_resource(self.session_type): return 0, 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 registered in # self.attrs, see Session.after_parsing if attribute in self.attrs: value = self.attrs[attribute] status = StatusCode.success if isinstance(value, tuple): getter = value[0] value, status = ( getter(attribute) if getter else (0, StatusCode.error_nonsupported_attribute) ) return value, status # 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, StatusCode.error_nonsupported_attribute def set_attribute( self, attribute: ResourceAttribute, attribute_state: Any ) -> StatusCode: """Set the attribute_state value for a given VISA attribute for this session. Does a few checks before and calls before dispatching to `_set_attribute`. Parameters ---------- attribute : ResourceAttribute Resource attribute for which the state query is made. attribute_state : Any Value. Returns ------- StatusCode The return value of the library call. """ # Check if the attribute value is defined. try: attr = attributes.AttributesByID[attribute] except KeyError: return StatusCode.error_nonsupported_attribute # Check if the attribute is defined for this session type. if not attr.in_resource(self.session_type): return StatusCode.error_nonsupported_attribute # Check if writing the attribute is allowed. if not attr.write: return StatusCode.error_attribute_read_only # First try to answer those attributes that are registered in # self.attrs, see Session.after_parsing if attribute in self.attrs: value = self.attrs[attribute] status = StatusCode.success if isinstance(value, tuple): setter = value[1] status = ( setter(attribute, attribute_state) if setter else StatusCode.error_nonsupported_attribute ) else: self.attrs[attribute] = attribute_state return status # Dispatch to `_set_attribute`, which must be implemented by subclasses try: return self._set_attribute(attribute, attribute_state) except ValueError: return StatusCode.error_nonsupported_attribute_state except NotImplementedError: e = UnknownAttribute(attribute) logger.exception(str(e)) return StatusCode.error_nonsupported_attribute except UnknownAttribute as e: logger.exception(str(e)) return StatusCode.error_nonsupported_attribute def _read( self, reader: Callable[[], bytes], count: int, end_indicator_checker: Callable[[bytes], bool], suppress_end_en: bool, termination_char: Optional[int], termination_char_en: bool, timeout_exception: Type[Exception], ) -> Tuple[bytes, StatusCode]: """Reads data from device or interface synchronously. Corresponds to viRead function of the VISA library. Parameters ---------- reader : Callable[[], bytes] Function to read one or more bytes. count : int Number of bytes to be read. end_indicator_checker : Callable[[bytes], bool] Function to check if the message is complete. suppress_end_en : bool Suppress end. termination_char : int Stop reading if this character is received. termination_char_en : bool Is termination char enabled. timeout_exception : Type[Exception] Exception to capture time out for the given interface. Returns ------- bytes Data read from the resource. StatusCode Return value of the library call. """ # NOTE: Some interfaces return not only a single byte but a complete # block for each read therefore we must handle the case that the # termination character is in the middle of the block or that the # maximum number of bytes is exceeded # Turn the termination_char store as an int in VISA attribute in a byte term_char = ( int_to_byte(termination_char) if termination_char is not None else b"" ) finish_time = None if self.timeout is None else (time.time() + self.timeout) out = bytearray() while True: try: current = reader() except timeout_exception: return out, StatusCode.error_timeout if current: out.extend(current) end_indicator_received = end_indicator_checker(current) if end_indicator_received: if not suppress_end_en: # RULE 6.1.1 return bytes(out), StatusCode.success else: if termination_char_en and (term_char in current): # RULE 6.1.2 # Return everything up to and including the termination # character return ( bytes(out[: out.index(term_char) + 1]), StatusCode.success_termination_character_read, ) elif len(out) >= count: # RULE 6.1.3 # Return at most the number of bytes requested return (bytes(out[:count]), StatusCode.success_max_count_read) if finish_time and time.time() > finish_time: return bytes(out), StatusCode.error_timeout def _get_timeout(self, attribute: ResourceAttribute) -> Tuple[int, StatusCode]: """Returns timeout calculated value from python way to VI_ way In VISA, the timeout is expressed in milliseconds or using the constants VI_TMO_INFINITE or VI_TMO_IMMEDIATE. In Python we store it as either None (VI_TMO_INFINITE), 0 (VI_TMO_IMMEDIATE) or as a floating point number in seconds. """ if self.timeout is None: ret_value = constants.VI_TMO_INFINITE elif self.timeout == 0: ret_value = constants.VI_TMO_IMMEDIATE else: ret_value = int(self.timeout * 1000.0) return ret_value, StatusCode.success def _set_timeout(self, attribute: ResourceAttribute, value: int): """Sets timeout calculated value from python way to VI_ way In VISA, the timeout is expressed in milliseconds or using the constants VI_TMO_INFINITE or VI_TMO_IMMEDIATE. In Python we store it as either None (VI_TMO_INFINITE), 0 (VI_TMO_IMMEDIATE) or as a floating point number in seconds. """ if value == constants.VI_TMO_INFINITE: self.timeout = None elif value == constants.VI_TMO_IMMEDIATE: self.timeout = 0 else: self.timeout = value / 1000.0 return StatusCode.success class UnavailableSession(Session): session_issue: ClassVar[str] def __init__(self, *args, **kwargs) -> None: raise ValueError(self.session_issue) def _get_attribute(self, attr): raise NotImplementedError() def _set_attribute(self, attr, value): raise NotImplementedError() def close(self): raise NotImplementedError() pyvisa-py-0.7.2/pyvisa_py/tcpip.py000066400000000000000000001421171457232645700172220ustar00rootroot00000000000000# -*- coding: utf-8 -*- """TCPIP Session implementation using Python Standard library. :copyright: 2014-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ import ipaddress import random import select import socket import time import warnings from typing import Any, Dict, List, Optional, Tuple, Type from pyvisa import attributes, constants, errors, rname from pyvisa.constants import BufferOperation, ResourceAttribute, StatusCode from . import common from .protocols import hislip, rpc, vxi11 from .sessions import Session, UnknownAttribute, VISARMSession # Let psutil be optional dependency try: import psutil # type: ignore except ImportError: psutil = None # Let zeroconf be optional dependency try: import zeroconf # type: ignore except ImportError: zeroconf = None # type: ignore # Let pyvicp be an optional dependency try: import pyvicp # type: ignore except ImportError: pyvicp = None # type: ignore # Conversion between VXI11 error codes and VISA status # TODO this is so far a best guess, in particular 6 and 29 are likely wrong VXI11_ERRORS_TO_VISA = { 0: StatusCode.success, # no_error 1: StatusCode.error_invalid_format, # syntax_error 3: StatusCode.error_connection_lost, # device_no_accessible 4: StatusCode.error_invalid_access_key, # invalid_link_identifier 5: StatusCode.error_invalid_parameter, # parameter_error 6: StatusCode.error_handler_not_installed, # channel_not_established 8: StatusCode.error_nonsupported_operation, # operation_not_supported 9: StatusCode.error_allocation, # out_of_resources 11: StatusCode.error_resource_locked, # device_locked_by_another_link 12: StatusCode.error_session_not_locked, # no_lock_held_by_this_link 15: StatusCode.error_timeout, # io_timeout 17: StatusCode.error_io, # io_error 23: StatusCode.error_abort, # abort 29: StatusCode.error_window_already_mapped, # channel_already_established } @Session.register(constants.InterfaceType.tcpip, "INSTR") class TCPIPInstrSession(Session): """A class to dispatch to VXI11 or HiSLIP, based on the protocol.""" def __new__( cls, resource_manager_session: VISARMSession, resource_name: str, parsed=None, open_timeout: Optional[int] = None, ): newcls: Type if parsed is None: parsed = rname.parse_resource_name(resource_name) if parsed.lan_device_name.lower().startswith("hislip"): newcls = TCPIPInstrHiSLIP else: newcls = TCPIPInstrVxi11 return newcls(resource_manager_session, resource_name, parsed, open_timeout) @staticmethod def list_resources(wait_time=1.0) -> List[str]: return TCPIPInstrVxi11.list_resources() + TCPIPInstrHiSLIP.list_resources() @classmethod def get_low_level_info(cls) -> str: vxi11 = "ok" if psutil is not None else "partial (psutil not installed)" hislip = "ok" if zeroconf is not None else "disabled (zeroconf not installed)" return ( "\n Resource discovery:" f"\n - VXI-11: {vxi11}" f"\n - hislip: {hislip}" ) class TCPIPInstrHiSLIP(Session): """A TCPIP Session built on socket standard library using HiSLIP protocol.""" # we don't decorate this class with Session.register() because we don't # want it to be registered in the _session_classes array, but we still # need to define session_type to make the set_attribute machinery work. session_type = (constants.InterfaceType.tcpip, "INSTR") # Override parsed to take into account the fact that this class is only used # for a specific kind of resource parsed: rname.TCPIPInstr @staticmethod def list_resources(wait_time=1.0) -> List[str]: resources = [] try: for host in get_services("_hislip._tcp.local.", wait_time=wait_time): resources.append(f"TCPIP::{host}::hislip0,4880::INSTR") except NotImplementedError: warnings.warn( "TCPIP::hislip resource discovery requires the zeroconf package " "to be installed... try 'pip install zeroconf'", UserWarning, ) return sorted(resources) def after_parsing(self) -> None: # TODO: board_number not handled if "," in self.parsed.lan_device_name: sub_address, port_str = self.parsed.lan_device_name.split(",") port = int(port_str) else: sub_address = self.parsed.lan_device_name port = 4880 self.interface = hislip.Instrument( self.parsed.host_address, timeout=self.timeout, port=port, sub_address=sub_address, ) # initialize the constant attributes self.attrs[ResourceAttribute.dma_allow_enabled] = constants.VI_FALSE self.attrs[ResourceAttribute.file_append_enabled] = constants.VI_FALSE self.attrs[ResourceAttribute.interface_instrument_name] = "TCPIP0 (HiSLIP)" self.attrs[ResourceAttribute.interface_number] = 0 self.attrs[ResourceAttribute.io_prot] = constants.VI_PROT_NORMAL self.attrs[ResourceAttribute.read_buffer_operation_mode] = ( constants.VI_FLUSH_DISABLE ) self.attrs[ResourceAttribute.resource_lock_state] = constants.VI_NO_LOCK self.attrs[ResourceAttribute.send_end_enabled] = constants.VI_TRUE self.attrs[ResourceAttribute.suppress_end_enabled] = constants.VI_FALSE self.attrs[ResourceAttribute.tcpip_address] = self.parsed.host_address self.attrs[ResourceAttribute.tcpip_device_name] = self.parsed.lan_device_name self.attrs[ResourceAttribute.tcpip_hislip_overlap_enable] = constants.VI_FALSE self.attrs[ResourceAttribute.tcpip_hislip_version] = 0x0010_0000 self.attrs[ResourceAttribute.tcpip_hostname] = self.parsed.host_address self.attrs[ResourceAttribute.tcpip_is_hislip] = constants.VI_TRUE self.attrs[ResourceAttribute.tcpip_nodelay] = constants.VI_TRUE self.attrs[ResourceAttribute.tcpip_port] = port self.attrs[ResourceAttribute.termchar] = ord("\n") self.attrs[ResourceAttribute.termchar_enabled] = constants.VI_FALSE self.attrs[ResourceAttribute.write_buffer_operation_mode] = ( constants.VI_FLUSH_WHEN_FULL ) # configure the variable attributes self.attrs[ResourceAttribute.tcpip_hislip_max_message_kb] = ( self.get_max_message_kb, self.set_max_message_kb, ) self.attrs[ResourceAttribute.tcpip_keepalive] = ( self.get_keepalive, self.set_keepalive, ) # TODO: additional attributes (someday) # self.attrs[ResourceAttribute.manufacturer_id] = 16711 # self.attrs[ResourceAttribute.max_queue_length] = 50 # self.attrs[ResourceAttribute.read_buffer_size] = 4096 # self.attrs[ResourceAttribute.resource_impl_version] = 0x0050_0c01 # self.attrs[ResourceAttribute.resource_manufacturer_id] = 4015 # self.attrs[ResourceAttribute.resource_manufacturer_name] = 'Rohde & Schwarz GmbH' # self.attrs[ResourceAttribute.resource_spec_version] = 0x0050_0800 # self.attrs[ResourceAttribute.user_data] = 0 # self.attrs[ResourceAttribute.write_buffer_size] = 4096 def get_max_message_kb( self, attribute: ResourceAttribute ) -> Tuple[int, StatusCode]: """Get the maximum HiSLIP message size in kilobytes.""" max_msg_size_kb = int(round(self.interface.max_msg_size / 1024)) return max_msg_size_kb, StatusCode.success def set_max_message_kb( self, attribute: ResourceAttribute, size_kb: int ) -> StatusCode: """Set the maximum HiSLIP message size in kilobytes.""" if size_kb < 1: raise ValueError("size must be >= 1 kilobyte") if size_kb > 0xFFFF_FFFF: raise ValueError("size exceeds the range in the VISA spec") self.interface.max_msg_size = int(round(size_kb * 1024)) return StatusCode.success def get_keepalive(self, attribute: ResourceAttribute) -> Tuple[bool, StatusCode]: """Is TCP keepalive enabled for the resource.""" return self.interface.keepalive, StatusCode.success def set_keepalive( self, attribute: ResourceAttribute, keepalive: bool ) -> StatusCode: """Turns TCP keepalive on/off for this connection.""" self.interface.keepalive = keepalive return StatusCode.success def close(self) -> StatusCode: self.interface.close() self.interface = None return StatusCode.success def _set_timeout(self, attribute: ResourceAttribute, value: int) -> StatusCode: status = super()._set_timeout(attribute, value) if hasattr(self.interface, "timeout"): self.interface.timeout = 1e-3 * value return status def read(self, count: int) -> Tuple[bytes, StatusCode]: """Reads data from device or interface synchronously. Corresponds to viRead function of the VISA library. Parameters ----------- count : int Number of bytes to be read. Returns ------- bytes Data read from the device StatusCode Return value of the library call. """ try: data = self.interface.receive(count) status = ( StatusCode.success_termination_character_read if self.interface._rmt else StatusCode.success_max_count_read if len(data) >= count else StatusCode.success ) except socket.timeout: data, status = b"", StatusCode.error_timeout return data, status def write(self, data: bytes) -> Tuple[int, StatusCode]: """Writes data to device or interface synchronously. Corresponds to viWrite function of the VISA library. Parameters ---------- data : bytes Data to be written. Returns ------- int Number of bytes actually transferred StatusCode Return value of the library call. """ self.interface.send(data) return len(data), StatusCode.success def clear(self) -> StatusCode: """Clears a device. Corresponds to viClear function of the VISA library. """ self.interface.device_clear() return StatusCode.success def _get_attribute(self, attribute: ResourceAttribute) -> Tuple[Any, StatusCode]: """Get the value for a given VISA attribute for this session. Use to implement custom logic for attributes. Parameters ---------- attribute : ResourceAttribute Attribute for which the state query is made Returns ------- Any State of the queried attribute for a specified resource StatusCode Return value of the library call. """ raise UnknownAttribute(attribute) def _set_attribute( self, attribute: ResourceAttribute, attribute_state: Any ) -> StatusCode: """Sets the state of an attribute. Corresponds to viSetAttribute function of the VISA library. Parameters ---------- attribute : constants.ResourceAttribute Attribute for which the state is to be modified. (Attributes.*) attribute_state : Any The state of the attribute to be set for the specified object. Returns ------- StatusCode Return value of the library call. """ raise UnknownAttribute(attribute) class Vxi11CoreClient(vxi11.CoreClient): """ make a connection using vxi11 protocol, optionally allowing the port number to be specified. although in general the port number must be obtained by querying the portmapper, in practice any given instrument typically always uses the same port number. this allows you to open that port on a firewall or set up an ssh tunnel to that port. """ def __init__( self, host: str, port: Optional[int], open_timeout: Optional[int] = 5000 ) -> None: self.packer = vxi11.Vxi11Packer() self.unpacker = vxi11.Vxi11Unpacker(b"") prog, vers = vxi11.DEVICE_CORE_PROG, vxi11.DEVICE_CORE_VERS if port is None: rpc.TCPClient.__init__(self, host, prog, vers, open_timeout) else: # bypass the portmapper lookup and use the specified port instead rpc.RawTCPClient.__init__(self, host, prog, vers, port, open_timeout) class TCPIPInstrVxi11(Session): """A TCPIP Session built on socket standard library using VXI-11 protocol.""" # we don't decorate this class with Session.register() because we don't # want it to be registered in the _session_classes array, but we still # need to define session_type to make the set_attribute machinery work. session_type = (constants.InterfaceType.tcpip, "INSTR") #: Maximum size of a chunk of data in bytes. max_recv_size: int #: Time to wait before erroring with a timeout when trying to acquire a lock lock_timeout: int #: Unique ID of the client used to authenticate messages. client_id: int #: ID of the link used for VXI-11 communication link: int # Override parsed to take into account the fact that this class is only used # for a specific kind of resource parsed: rname.TCPIPInstr # Setting if keepalive has been activated keepalive: bool @staticmethod def list_resources() -> List[str]: broadcast_addr = [] if psutil is not None: # Get broadcast address for each interface for interface, snics in psutil.net_if_addrs().items(): for snic in snics: if snic.family is socket.AF_INET: addr = snic.address mask = snic.netmask network = ipaddress.IPv4Network(addr + "/" + mask, strict=False) broadcast_addr.append(str(network.broadcast_address)) else: # If psutil unavailable fallback to default interface broadcast_addr.append("255.255.255.255") warnings.warn( "TCPIP:instr resource discovery is limited to the default interface." "Install psutil: pip install psutil if you want to scan all interfaces.", UserWarning, ) pmap_list = [rpc.BroadcastUDPPortMapperClient(ip) for ip in broadcast_addr] for pmap in list(pmap_list): pmap.set_timeout(0) try: pmap.send_port( (vxi11.DEVICE_CORE_PROG, vxi11.DEVICE_CORE_VERS, rpc.IPPROTO_TCP, 0) ) except rpc.RPCError: pmap_list.remove(pmap) # Timeout for responses time.sleep(1) all_res = [] for pmap in pmap_list: try: resp = pmap.recv_port( (vxi11.DEVICE_CORE_PROG, vxi11.DEVICE_CORE_VERS, rpc.IPPROTO_TCP, 0) ) except rpc.RPCError: pass else: res = [r[1][0] for r in resp if r[0] > 0] res = sorted( res, key=lambda ip: tuple(int(part) for part in ip.split(".")) ) # TODO: Detect GPIB over TCPIP res = ["TCPIP::{}::INSTR".format(host) for host in res] all_res.extend(res) return all_res def after_parsing(self) -> None: # TODO: board_number not handled host_address = self.parsed.host_address if "," in host_address: host_address, port_str = host_address.split(",") port = int(port_str) else: port = None try: self.interface = Vxi11CoreClient(host_address, port, self.open_timeout) except rpc.RPCError: raise errors.VisaIOError(constants.VI_ERROR_RSRC_NFOUND) # vxi11 expect all timeouts to be expressed in ms and should be integers self.lock_timeout = 10000 self.client_id = random.getrandbits(31) self.keepalive = False 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 self.attrs[ResourceAttribute.tcpip_is_hislip] = False self.attrs[ResourceAttribute.tcpip_address] = self.parsed.host_address self.attrs[ResourceAttribute.tcpip_hostname] = "" self.attrs[ResourceAttribute.tcpip_device_name] = self.parsed.lan_device_name for name in ("SEND_END_EN", "TERMCHAR", "TERMCHAR_EN"): attribute = getattr(constants, "VI_ATTR_" + name) self.attrs[attribute] = attributes.AttributesByID[attribute].default def close(self) -> StatusCode: try: self.interface.destroy_link(self.link) except (errors.VisaIOError, socket.error, rpc.RPCError) as e: print("Error closing VISA link: {}".format(e)) self.interface.close() self.link = 0 self.interface = None return StatusCode.success def read(self, count: int) -> Tuple[bytes, StatusCode]: """Reads data from device or interface synchronously. Corresponds to viRead function of the VISA library. Parameters ---------- count : int Number of bytes to be read. Returns ------- bytes Data read StatusCode Return value of the library call. """ if count < self.max_recv_size: chunk_length = count else: chunk_length = self.max_recv_size if self.get_attribute(ResourceAttribute.termchar_enabled)[0]: term_char, _ = self.get_attribute(ResourceAttribute.termchar) flags = vxi11.OP_FLAG_TERMCHAR_SET else: term_char = flags = 0 read_data = bytearray() reason = 0 # Stop on end of message or when a termination character has been # encountered. end_reason = vxi11.RX_END | vxi11.RX_CHR read_fun = self.interface.device_read status = StatusCode.success timeout = self._io_timeout start_time = time.time() while reason & end_reason == 0: # Decrease timeout so that the total timeout does not get larger # than the specified timeout. timeout = max(0, timeout - int((time.time() - start_time) * 1000)) error, reason, data = read_fun( self.link, chunk_length, timeout, self.lock_timeout, flags, term_char ) if error == vxi11.ErrorCodes.io_timeout: return bytes(read_data), StatusCode.error_timeout elif error: return bytes(read_data), StatusCode.error_io read_data.extend(data) count -= len(data) 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: bytes) -> Tuple[int, StatusCode]: """Writes data to device or interface synchronously. Corresponds to viWrite function of the VISA library. Parameters ---------- data : bytes Data to be written. Returns ------- int Number of bytes actually transferred StatusCode Return value of the library call. """ try: flags = 0 num = len(data) offset = 0 while num > 0: if num <= self.max_recv_size: flags |= vxi11.OP_FLAG_END block = data[offset : offset + self.max_recv_size] error, size = self.interface.device_write( self.link, self._io_timeout, self.lock_timeout, flags, block ) if error == vxi11.ErrorCodes.io_timeout: return offset, StatusCode.error_timeout elif error or size < len(block): return offset, StatusCode.error_io offset += size num -= size return offset, StatusCode.success except vxi11.Vxi11Error: return 0, StatusCode.error_timeout def _get_attribute(self, attribute: ResourceAttribute) -> Tuple[Any, StatusCode]: """Get the value for a given VISA attribute for this session. Use to implement custom logic for attributes. Parameters ---------- attribute : Resource attribute for which the state query is made Returns ------- Any The state of the queried attribute for a specified resource StatusCode Return value of the library call. """ # This is an abuse of the VISA standard if attribute == constants.VI_ATTR_TCPIP_KEEPALIVE: return self.keepalive, StatusCode.success elif attribute == constants.VI_ATTR_SUPPRESS_END_EN: raise NotImplementedError raise UnknownAttribute(attribute) def _set_attribute( self, attribute: ResourceAttribute, attribute_state: Any ) -> StatusCode: """Sets the state of an attribute. Corresponds to viSetAttribute function of the VISA library. Parameters ---------- attribute : ResourceAttribute Attribute for which the state is to be modified. attribute_state : Any The state of the attribute to be set for the specified object. Returns ------- StatusCode Return value of the library call. """ # In case of an environment with idle socket garbage collection (like docker) # sockets need to be kept alive. Set pyvisa.constants.ResourceAttribute.tcpip_keepalive to enable # keepalive packets even for VXI11 protocol. To read more on this issue # https://tech.xing.com/a-reason-for-unexplained-connection-timeouts-on-kubernetes-docker-abd041cf7e02 if attribute == constants.VI_ATTR_TCPIP_KEEPALIVE: if attribute_state is True: self.interface.sock.setsockopt( socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1 ) self.interface.sock.setsockopt( socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60 ) self.interface.sock.setsockopt( socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 60 ) self.interface.sock.setsockopt( socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5 ) self.keepalive = True elif attribute_state is False: self.interface.sock.setsockopt( socket.SOL_SOCKET, socket.SO_KEEPALIVE, 0 ) self.keepalive = False else: return StatusCode.error_nonsupported_format return StatusCode.success raise UnknownAttribute(attribute) def assert_trigger(self, protocol: constants.TriggerProtocol): """Asserts software or hardware trigger. Corresponds to viAssertTrigger function of the VISA library. Parameters ---------- protocol : constants.TriggerProtocol Trigger protocol to use during assertion. Only default is supported. Returns ------- StatusCode Return value of the library call. """ # XXX make this nicer (either validate protocol or pass it) error = self.interface.device_trigger( self.link, 0, self.lock_timeout, self._io_timeout ) return VXI11_ERRORS_TO_VISA[error] def clear(self) -> StatusCode: """Clears a device. Corresponds to viClear function of the VISA library. """ error = self.interface.device_clear( self.link, 0, self.lock_timeout, self._io_timeout ) return VXI11_ERRORS_TO_VISA[error] def read_stb(self) -> Tuple[int, StatusCode]: """Reads a status byte of the service request. Corresponds to viReadSTB function of the VISA library. Returns ------- int Service request status byte StatusCode Return value of the library call. """ error, stb = self.interface.device_read_stb( self.link, 0, self.lock_timeout, self._io_timeout ) return stb, VXI11_ERRORS_TO_VISA[error] def lock( self, lock_type: constants.Lock, timeout: int, requested_key: Optional[str] = None, ) -> Tuple[str, constants.StatusCode]: """Establishes an access mode to the specified resources. Corresponds to viLock function of the VISA library. Parameters ---------- session : VISASession Unique logical identifier to a session. lock_type : constants.Lock Specifies the type of lock requested. timeout : int Absolute time period (in milliseconds) that a resource waits to get unlocked by the locking session before returning an error. requested_key : Optional[str], optional Requested locking key in the case of a shared lock. For an exclusive lock it should be None. Returns ------- Optional[str] Key that can then be passed to other sessions to share the lock, or None for an exclusive lock. StatusCode Return value of the library call. """ # TODO: lock type not implemented flags = 0 error = self.interface.device_lock(self.link, flags, self.lock_timeout) return "", VXI11_ERRORS_TO_VISA[error] def unlock(self) -> constants.StatusCode: """Relinquish a lock for the specified resource. Corresponds to viUnlock function of the VISA library. Returns ------- StatusCode Return value of the library call. """ error = self.interface.device_unlock(self.link) return VXI11_ERRORS_TO_VISA[error] def _set_timeout(self, attribute: ResourceAttribute, value: int) -> StatusCode: """Sets timeout calculated value from python way to VI_ way""" if value == constants.VI_TMO_INFINITE: self.timeout = None self._io_timeout = 2**32 - 1 elif value == constants.VI_TMO_IMMEDIATE: self.timeout = 0 self._io_timeout = 0 else: self.timeout = value / 1000.0 self._io_timeout = int(self.timeout * 1000) return StatusCode.success class TCPIPInstrVicp(Session): """VICP Session that uses pyvicp to do the low level communication.""" # Override parsed to take into account the fact that this class is only used # for a specific kind of resource parsed: rname.VICPInstr @staticmethod def list_resources(wait_time=1.0) -> List[str]: resources = [] try: services = get_services("_lxi._tcp.local.", wait_time=wait_time) except NotImplementedError: warnings.warn( "VICP resources discovery requires the zeroconf package to be " "installed... try 'pip install zeroconf'", UserWarning, ) return [] for host, properties in services.items(): if properties["Manufacturer"].lower().startswith("lecroy"): resources.append(f"VICP::{host}::INSTR") return sorted(resources) def after_parsing(self) -> None: # TODO: board_number not handled if pyvicp is None: raise NotImplementedError( "VICP requires the pyvicp package to be installed... " "try 'pip install pyvicp'" ) host_address = self.parsed.host_address if "," in host_address: host_address, port_str = host_address.split(",") port = int(port_str) else: port = 1861 self.interface = pyvicp.Client( self.parsed.host_address, port, timeout=self.timeout ) # initialize the constant attributes for name in ("SEND_END_EN", "TERMCHAR", "TERMCHAR_EN"): attribute = getattr(constants, "VI_ATTR_" + name) self.attrs[attribute] = attributes.AttributesByID[attribute].default self.attrs[ResourceAttribute.dma_allow_enabled] = constants.VI_FALSE self.attrs[ResourceAttribute.file_append_enabled] = constants.VI_FALSE self.attrs[ResourceAttribute.interface_instrument_name] = "TCPIP0 (VICP)" self.attrs[ResourceAttribute.interface_number] = 0 self.attrs[ResourceAttribute.io_prot] = constants.VI_PROT_NORMAL self.attrs[ResourceAttribute.read_buffer_operation_mode] = ( constants.VI_FLUSH_DISABLE ) self.attrs[ResourceAttribute.resource_lock_state] = constants.VI_NO_LOCK self.attrs[ResourceAttribute.suppress_end_enabled] = constants.VI_FALSE self.attrs[ResourceAttribute.tcpip_address] = self.parsed.host_address self.attrs[ResourceAttribute.tcpip_hostname] = self.parsed.host_address self.attrs[ResourceAttribute.tcpip_is_hislip] = constants.VI_FALSE self.attrs[ResourceAttribute.tcpip_nodelay] = constants.VI_TRUE self.attrs[ResourceAttribute.tcpip_port] = port self.attrs[ResourceAttribute.write_buffer_operation_mode] = ( constants.VI_FLUSH_WHEN_FULL ) # configure the variable attributes self.attrs[ResourceAttribute.tcpip_keepalive] = ( self.get_keepalive, self.set_keepalive, ) # TODO: additional attributes (someday) # self.attrs[ResourceAttribute.manufacturer_id] = 16711 # self.attrs[ResourceAttribute.max_queue_length] = 50 # self.attrs[ResourceAttribute.read_buffer_size] = 4096 # self.attrs[ResourceAttribute.resource_impl_version] = 0x0050_0c01 # self.attrs[ResourceAttribute.resource_manufacturer_id] = 4015 # self.attrs[ResourceAttribute.resource_manufacturer_name] = 'Rohde & Schwarz GmbH' # self.attrs[ResourceAttribute.resource_spec_version] = 0x0050_0800 # self.attrs[ResourceAttribute.user_data] = 0 # self.attrs[ResourceAttribute.write_buffer_size] = 4096 def get_keepalive(self, attribute: ResourceAttribute) -> Tuple[bool, StatusCode]: """Is TCP keepalive enabled for the resource.""" return self.interface.keepalive, StatusCode.success def set_keepalive( self, attribute: ResourceAttribute, keepalive: bool ) -> StatusCode: """Turns TCP keepalive on/off for this connection.""" self.interface.keepalive = keepalive return StatusCode.success def close(self) -> StatusCode: self.interface.close() self.interface = None return StatusCode.success def read(self, count: int) -> Tuple[bytes, StatusCode]: """Reads data from device or interface synchronously. Corresponds to viRead function of the VISA library. Parameters ----------- count : int Number of bytes to be read. Returns ------- bytes Data read from the device StatusCode Return value of the library call. """ try: data = self.interface.receive(count) except socket.timeout: return b"", StatusCode.error_timeout if len(data) >= count: return data, StatusCode.success_max_count_read else: return data, StatusCode.success_termination_character_read def write(self, data: bytes) -> Tuple[int, StatusCode]: """Writes data to device or interface synchronously. Corresponds to viWrite function of the VISA library. Parameters ---------- data : bytes Data to be written. Returns ------- int Number of bytes actually transferred StatusCode Return value of the library call. """ self.interface.send(data) return len(data), StatusCode.success def clear(self) -> StatusCode: """Clears a device. Corresponds to viClear function of the VISA library. """ self.interface.device_clear() return StatusCode.success def _set_timeout(self, attribute: ResourceAttribute, value: int) -> StatusCode: status = super()._set_timeout(attribute, value) if hasattr(self.interface, "timeout"): self.interface.timeout = 1e-3 * value return status def _get_attribute(self, attribute: ResourceAttribute) -> Tuple[Any, StatusCode]: """Get the value for a given VISA attribute for this session. Use to implement custom logic for attributes. Parameters ---------- attribute : ResourceAttribute Attribute for which the state query is made Returns ------- Any State of the queried attribute for a specified resource StatusCode Return value of the library call. """ raise UnknownAttribute(attribute) def _set_attribute( self, attribute: ResourceAttribute, attribute_state: Any ) -> StatusCode: """Sets the state of an attribute. Corresponds to viSetAttribute function of the VISA library. Parameters ---------- attribute : constants.ResourceAttribute Attribute for which the state is to be modified. (Attributes.*) attribute_state : Any The state of the attribute to be set for the specified object. Returns ------- StatusCode Return value of the library call. """ raise UnknownAttribute(attribute) if pyvicp is not None: Session.register(constants.InterfaceType.vicp, "INSTR")(TCPIPInstrVicp) else: Session.register_unavailable( constants.InterfaceType.vicp, "INSTR", "Please install PyVICP to use this resource type.", ) @Session.register(constants.InterfaceType.tcpip, "SOCKET") class TCPIPSocketSession(Session): """A TCPIP Session that uses the network standard library to do the low level communication. """ # Details about implementation: # On Windows, select is not interrupted by KeyboardInterrupt, to avoid # blocking for very long time, we use a decreasing timeout in select. # A minimum select timeout which prevents using too short select interval # is also calculated and select timeout is not lower that that minimum # timeout. The absolute minimum is 1 ms as a consequence. # This is valid for connect and read operations #: Maximum size of a chunk of data in bytes. max_recv_size: int # Override parsed to take into account the fact that this class is only used # for a specific kind of resource parsed: rname.TCPIPSocket @staticmethod def list_resources() -> List[str]: # TODO: is there a way to get this? return [] def after_parsing(self) -> None: # TODO: board_number not handled ret_status = self._connect() if ret_status != StatusCode.success: self.close() raise Exception("could not connect: {0}".format(str(ret_status))) self.max_recv_size = 4096 # This buffer is used to store the bytes that appeared after # termination char self._pending_buffer = bytearray() self.attrs[ResourceAttribute.tcpip_address] = self.parsed.host_address self.attrs[ResourceAttribute.tcpip_port] = self.parsed.port self.attrs[ResourceAttribute.interface_number] = self.parsed.board self.attrs[ResourceAttribute.tcpip_nodelay] = ( self._get_tcpip_nodelay, self._set_attribute, ) self.attrs[ResourceAttribute.tcpip_hostname] = "" self.attrs[ResourceAttribute.tcpip_keepalive] = ( self._get_tcpip_keepalive, self._set_tcpip_keepalive, ) # to use default as ni visa driver (NI-VISA 15.0) self.attrs[ResourceAttribute.suppress_end_enabled] = True for name in ("TERMCHAR", "TERMCHAR_EN"): attribute = getattr(constants, "VI_ATTR_" + name) self.attrs[attribute] = attributes.AttributesByID[attribute].default def _connect(self) -> StatusCode: timeout = self.open_timeout / 1000.0 if self.open_timeout else 10.0 try: self.interface = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.interface.setblocking(False) self.interface.connect_ex((self.parsed.host_address, int(self.parsed.port))) except Exception as e: raise Exception("could not connect: {0}".format(str(e))) finally: self.interface.setblocking(True) # minimum is in interval 100 - 500ms based on timeout min_select_timeout = max(min(timeout / 10.0, 0.5), 0.1) # initial 'select_timout' is half of timeout or max 2 secs # (max blocking time). min is from 'min_select_timeout' select_timout = max(min(timeout / 2.0, 2.0), min_select_timeout) # time, when loop shall finish finish_time = time.time() + timeout while True: # use select to wait for socket ready, max `select_timout` seconds r, w, x = select.select( [self.interface], [self.interface], [], select_timout ) if self.interface in r or self.interface in w: return StatusCode.success if time.time() >= finish_time: # reached timeout return StatusCode.error_timeout # `select_timout` decreased to 50% of previous or # min_select_timeout select_timout = max(select_timout / 2.0, min_select_timeout) def close(self) -> StatusCode: self.interface.close() self.interface = None return StatusCode.success def read(self, count: int) -> Tuple[bytes, StatusCode]: """Reads data from device or interface synchronously. Corresponds to viRead function of the VISA library. Parameters ----------- count : int Number of bytes to be read. Returns ------- bytes Data read from the device StatusCode Return value of the library call. """ if count < self.max_recv_size: chunk_length = count else: chunk_length = self.max_recv_size term_char, _ = self.get_attribute(ResourceAttribute.termchar) term_byte = common.int_to_byte(term_char) if term_char is not None else b"" term_char_en, _ = self.get_attribute(ResourceAttribute.termchar_enabled) suppress_end_en, _ = self.get_attribute(ResourceAttribute.suppress_end_enabled) read_fun = self.interface.recv # minimum is in interval 1 - 100ms based on timeout, 1sec if no timeout # defined min_select_timeout = ( 1 if self.timeout is None else max(min(self.timeout / 100.0, 0.1), 0.001) ) # initial 'select_timout' is half of timeout or max 2 secs # (max blocking time). min is from 'min_select_timeout' select_timout = ( 2.0 if self.timeout is None else max(min(self.timeout / 2.0, 2.0), min_select_timeout) ) # time, when loop shall finish, None means never ending story if no # data arrives finish_time = None if self.timeout is None else (time.time() + self.timeout) while True: # check, if we have any data received (from pending buffer or # further reading) if term_char_en and term_byte in self._pending_buffer: term_byte_index = self._pending_buffer.index(term_byte) + 1 if term_byte_index > count: term_byte_index = count status = StatusCode.success_max_count_read else: status = StatusCode.success_termination_character_read out = bytes(self._pending_buffer[:term_byte_index]) self._pending_buffer = self._pending_buffer[term_byte_index:] return out, status if len(self._pending_buffer) >= count: out = bytes(self._pending_buffer[:count]) self._pending_buffer = self._pending_buffer[count:] return out, StatusCode.success_max_count_read # use select to wait for read ready, max `select_timout` seconds r, w, x = select.select([self.interface], [], [], select_timout) read_data = b"" if self.interface in r: read_data = read_fun(chunk_length) self._pending_buffer.extend(read_data) if not read_data: # can't read chunk or timeout if self._pending_buffer and not suppress_end_en: # we have some data without termchar but no further data # expected out = bytes(self._pending_buffer[:count]) self._pending_buffer = self._pending_buffer[count:] return out, StatusCode.success if finish_time and time.time() >= finish_time: # reached timeout out = bytes(self._pending_buffer[:count]) self._pending_buffer = self._pending_buffer[count:] return out, StatusCode.error_timeout # `select_timout` decreased to 50% of previous or # min_select_timeout select_timout = max(select_timout / 2.0, min_select_timeout) def write(self, data: bytes) -> Tuple[int, StatusCode]: """Writes data to device or interface synchronously. Corresponds to viWrite function of the VISA library. Parameters ---------- data : bytes Data to be written. Returns ------- int Number of bytes actually transferred StatusCode Return value of the library call. """ 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: return offset, StatusCode.error_io if size < len(block): return offset, StatusCode.error_io offset += size num -= size return offset, StatusCode.success def clear(self) -> StatusCode: """Clears a device. Corresponds to viClear function of the VISA library. """ self._pending_buffer.clear() while True: r, w, x = select.select([self.interface], [], [], 0.1) if not r: break r[0].recv(4096) return StatusCode.success def flush(self, mask: BufferOperation) -> StatusCode: """Flush the specified buffers. Corresponds to viFlush function of the VISA library. Parameters ---------- mask : constants.BufferOperation Specifies the action to be taken with flushing the buffer. The values can be combined using the | operator. However multiple operations on a single buffer cannot be combined. Returns ------- constants.StatusCode Return value of the library call. """ if mask & BufferOperation.discard_read_buffer: self.clear() if ( mask & BufferOperation.discard_read_buffer_no_io or mask & BufferOperation.discard_receive_buffer or mask & BufferOperation.discard_receive_buffer2 ): self._pending_buffer.clear() if ( mask & BufferOperation.flush_write_buffer or mask & BufferOperation.flush_transmit_buffer or mask & BufferOperation.discard_write_buffer or mask & BufferOperation.discard_transmit_buffer ): pass return StatusCode.success def _get_tcpip_nodelay( self, attribute: ResourceAttribute ) -> Tuple[constants.VisaBoolean, StatusCode]: if self.interface: value = self.interface.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) return ( constants.VisaBoolean.true if value == 1 else constants.VisaBoolean.false, StatusCode.success, ) return constants.VisaBoolean.false, StatusCode.error_nonsupported_attribute def _set_tcpip_nodelay( self, attribute: ResourceAttribute, attribute_state: bool ) -> StatusCode: if self.interface: self.interface.setsockopt( socket.IPPROTO_TCP, socket.TCP_NODELAY, 1 if attribute_state else 0 ) return StatusCode.success return StatusCode.error_nonsupported_attribute def _get_tcpip_keepalive( self, attribute: ResourceAttribute ) -> Tuple[constants.VisaBoolean, StatusCode]: if self.interface: value = self.interface.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE) return ( constants.VisaBoolean.true if value == 1 else constants.VisaBoolean.false, StatusCode.success, ) return constants.VisaBoolean.false, StatusCode.error_nonsupported_attribute def _set_tcpip_keepalive( self, attribute: ResourceAttribute, attribute_state: bool ) -> StatusCode: if self.interface: self.interface.setsockopt( socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1 if attribute_state else 0 ) self.interface.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60) self.interface.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 60) self.interface.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5) return StatusCode.success return StatusCode.error_nonsupported_attribute def _get_attribute(self, attribute: ResourceAttribute) -> Tuple[Any, StatusCode]: """Get the value for a given VISA attribute for this session. Use to implement custom logic for attributes. Parameters ---------- attribute : ResourceAttribute Attribute for which the state query is made Returns ------- Any State of the queried attribute for a specified resource StatusCode Return value of the library call. """ raise UnknownAttribute(attribute) def _set_attribute( self, attribute: ResourceAttribute, attribute_state: Any ) -> StatusCode: """Sets the state of an attribute. Corresponds to viSetAttribute function of the VISA library. Parameters ---------- attribute : constants.ResourceAttribute Attribute for which the state is to be modified. (Attributes.*) attribute_state : Any The state of the attribute to be set for the specified object. Returns ------- StatusCode Return value of the library call. """ raise UnknownAttribute(attribute) def get_services(service_type: str, wait_time: float = 0.1) -> Dict[str, dict]: if zeroconf is None: raise NotImplementedError( "Service discovery requires the zeroconf package to be installed... " "try 'pip install zeroconf'" ) class MyListener(zeroconf.ServiceListener): def __init__(self, *args, **kwargs): self.services = {} super().__init__(*args, **kwargs) def remove_service(self, zc: zeroconf.Zeroconf, type_: str, name: str) -> None: del self.services[name] def add_service(self, zc: zeroconf.Zeroconf, type_: str, name: str) -> None: info = zc.get_service_info(type_, name) if info is None: return properties = {} for key, val in info.properties.items(): if key == b"txtvers": continue properties[key.decode()] = val.decode() ipaddr = ipaddress.ip_address(info.addresses[0]) self.services[str(ipaddr)] = properties update_service = add_service zero_conf = zeroconf.Zeroconf() listener = MyListener() browser = zeroconf.ServiceBrowser(zero_conf, service_type, listener, delay=0) time.sleep(wait_time) browser.cancel() zero_conf.close() return listener.services pyvisa-py-0.7.2/pyvisa_py/testsuite/000077500000000000000000000000001457232645700175545ustar00rootroot00000000000000pyvisa-py-0.7.2/pyvisa_py/testsuite/__init__.py000066400000000000000000000011271457232645700216660ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import unittest # Set the environment variable to use PyVISA-py as backend os.environ["PYVISA_LIBRARY"] = "@py" def testsuite(): """A testsuite that has all the pyvisa-py 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() -> unittest.TestResult: """Run all tests.""" test_runner = unittest.TextTestRunner() return test_runner.run(testsuite()) pyvisa-py-0.7.2/pyvisa_py/testsuite/keysight_assisted_tests/000077500000000000000000000000001457232645700245245ustar00rootroot00000000000000pyvisa-py-0.7.2/pyvisa_py/testsuite/keysight_assisted_tests/__init__.py000066400000000000000000000007771457232645700266500ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Test relying on the Keysight virtual instrument. The PyVISA builbot is connected to a fake instrument implemented using the Keysight Virtual Instrument IO Test software. For this part of the testsuite to be run, you need to set the PYVISA_KEYSIGHT_VIRTUAL_INSTR environment value. See pyvisa/testsuite/keysight_assisted_tests/__init__.py for more details. :copyright: 2014-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ pyvisa-py-0.7.2/pyvisa_py/testsuite/keysight_assisted_tests/test_resource_manager.py000066400000000000000000000024251457232645700314610ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Test the Resource manager.""" import pytest from pyvisa.rname import ResourceName from pyvisa.testsuite.keysight_assisted_tests import ( RESOURCE_ADDRESSES, copy_func, require_virtual_instr, ) from pyvisa.testsuite.keysight_assisted_tests.test_resource_manager import ( TestResourceManager as BaseTestResourceManager, TestResourceParsing as BaseTestResourceParsing, ) @require_virtual_instr class TestPyResourceManager(BaseTestResourceManager): """ """ def test_list_resource(self): """Test listing the available resources. The bot supports only TCPIP and of those resources we expect to be able to list only INSTR resources not SOCKET. """ # Default settings resources = self.rm.list_resources() for v in (v for v in RESOURCE_ADDRESSES.values() if v.endswith("INSTR")): assert str(ResourceName.from_string(v)) in resources test_last_status = pytest.mark.xfail( copy_func(BaseTestResourceManager.test_last_status) ) test_opening_resource_with_lock = pytest.mark.xfail( copy_func(BaseTestResourceManager.test_opening_resource_with_lock) ) @require_virtual_instr class TestPyResourceParsing(BaseTestResourceParsing): """ """ pass pyvisa-py-0.7.2/pyvisa_py/testsuite/keysight_assisted_tests/test_tcpip_resources.py000066400000000000000000000110431457232645700313450ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Test the TCPIP based resources.""" import socket import pytest from pyvisa.constants import ResourceAttribute from pyvisa.testsuite.keysight_assisted_tests import copy_func, require_virtual_instr from pyvisa.testsuite.keysight_assisted_tests.test_tcpip_resources import ( TestTCPIPInstr as TCPIPInstrBaseTest, TestTCPIPSocket as TCPIPSocketBaseTest, ) @require_virtual_instr class TestTCPIPInstr(TCPIPInstrBaseTest): """Test pyvisa-py against a TCPIP INSTR resource.""" #: Type of resource being tested in this test case. #: See RESOURCE_ADDRESSES in the __init__.py file of this package for #: acceptable values RESOURCE_TYPE = "TCPIP::INSTR" #: Minimal timeout value accepted by the resource. When setting the timeout #: to VI_TMO_IMMEDIATE, Visa (Keysight at least) may actually use a #: different value depending on the values supported by the resource. MINIMAL_TIMEOUT = 0 # XXX should we try to have this match VISA ? # XXX Skip test clear to see if it has some bad side effect test_clear = pytest.mark.skip(copy_func(TCPIPInstrBaseTest.test_clear)) test_wrapping_handler = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_wrapping_handler) ) test_managing_visa_handler = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_managing_visa_handler) ) test_wait_on_event = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_wait_on_event) ) test_wait_on_event_timeout = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_wait_on_event_timeout) ) test_getting_unknown_buffer = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_getting_unknown_buffer) ) test_manual_async_read = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_manual_async_read) ) test_uninstall_all_handlers = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_uninstall_all_handlers) ) test_handler_clean_up_on_resource_del = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_handler_clean_up_on_resource_del) ) test_uninstalling_missing_visa_handler = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_uninstalling_missing_visa_handler) ) test_handling_invalid_handler = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_handling_invalid_handler) ) test_write_raw_read_bytes = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_write_raw_read_bytes) ) test_io_prot_attr = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_io_prot_attr) ) test_shared_locking = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_shared_locking) ) test_timeout = pytest.mark.xfail(copy_func(TCPIPInstrBaseTest.test_timeout)) test_attribute_handling = pytest.mark.xfail( copy_func(TCPIPInstrBaseTest.test_attribute_handling) ) def test_keepalive_attribute_vxi11(self): assert self.instr.visalib.sessions[self.instr.session].keepalive is False self.instr.set_visa_attribute(ResourceAttribute.tcpip_keepalive, True) assert self.instr.visalib.sessions[self.instr.session].keepalive is True assert ( self.instr.visalib.sessions[self.instr.session].interface.sock.getsockopt( socket.SOL_SOCKET, socket.SO_KEEPALIVE ) == 1 ) self.instr.set_visa_attribute(ResourceAttribute.tcpip_keepalive, False) assert self.instr.visalib.sessions[self.instr.session].keepalive is False assert ( self.instr.visalib.sessions[self.instr.session].interface.sock.getsockopt( socket.SOL_SOCKET, socket.SO_KEEPALIVE ) == 0 ) @require_virtual_instr class TestTCPIPSocket(TCPIPSocketBaseTest): """Test pyvisa-py against a TCPIP SOCKET resource.""" #: Type of resource being tested in this test case. #: See RESOURCE_ADDRESSES in the __init__.py file of this package for #: acceptable values RESOURCE_TYPE = "TCPIP::SOCKET" #: Minimal timeout value accepted by the resource. When setting the timeout #: to VI_TMO_IMMEDIATE, Visa (Keysight at least) may actually use a #: different value depending on the values supported by the resource. MINIMAL_TIMEOUT = 1 test_timeout = pytest.mark.xfail(copy_func(TCPIPSocketBaseTest.test_timeout)) test_attribute_handling = pytest.mark.xfail( copy_func(TCPIPSocketBaseTest.test_attribute_handling) ) test_stb = pytest.mark.xfail(copy_func(TCPIPSocketBaseTest.test_stb)) pyvisa-py-0.7.2/pyvisa_py/testsuite/test_common.py000066400000000000000000000105551457232645700224630ustar00rootroot00000000000000from typing import List, Optional import pytest from pyvisa_py import common # TODO(anyone): This is copypasta from `pyvisa-sim` project - find a way to # reduce duplication, probably in that project instead of here. @pytest.mark.parametrize( "bits, want", [ (0, 0b0), (1, 0b1), (5, 0b0001_1111), (7, 0b0111_1111), (8, 0b1111_1111), (11, 0b0111_1111_1111), ], ) def test_create_bitmask(bits, want): got = common._create_bitmask(bits) assert got == want # TODO(anyone): This is copypasta from `pyvisa-sim` project - find a way to # reduce duplication, probably in that project instead of here. @pytest.mark.parametrize( "data, data_bits, send_end, want", [ (b"\x01", None, False, b"\x01"), (b"hello world!", None, False, b"hello world!"), # Only apply the mask (b"\x03", 2, None, b"\x03"), # 0b0000_0011 --> 0b0000_0011 (b"\x04", 2, None, b"\x00"), # 0b0000_0100 --> 0b0000_0000 (b"\xff", 5, None, b"\x1f"), # 0b1111_1111 --> 0b0001_1111 (b"\xfe", 7, None, b"\x7e"), # 0b1111_1110 --> 0b0111_1110 (b"\xfe", 8, None, b"\xfe"), # 0b1111_1110 --> 0b1111_1110 (b"\xff", 9, None, b"\xff"), # 0b1111_1111 --> 0b1111_1111 # Always set highest bit *of data_bits* to 0 (b"\x04", 2, False, b"\x00"), # 0b0000_0100 --> 0b0000_0000 (b"\x04", 3, False, b"\x00"), # 0b0000_0100 --> 0b0000_0000 (b"\x05", 3, False, b"\x01"), # 0b0000_0101 --> 0b0000_0001 (b"\xff", 7, False, b"\x3f"), # 0b1111_1111 --> 0b0011_1111 (b"\xff", 8, False, b"\x7f"), # 0b1111_1111 --> 0b0111_1111 # Always set highest bit *of data_bits* to 1 (b"\x04", 2, True, b"\x02"), # 0b0000_0100 --> 0b0000_0010 (b"\x04", 3, True, b"\x04"), # 0b0000_0100 --> 0b0000_0100 (b"\x01", 3, True, b"\x05"), # 0b0000_0001 --> 0b0000_0101 (b"\x9f", 7, True, b"\x5f"), # 0b1001_1111 --> 0b0101_1111 (b"\x9f", 8, True, b"\x9f"), # 0b1001_1111 --> 0b1001_1111 # data_bits >8 bits act like data_bits=8, as type(data) is "bytes" # which is limited 8 bits per character. (b"\xff", 9, None, b"\xff"), (b"\xff", 9, False, b"\x7f"), (b"\xff", 9, True, b"\xff"), # send_end=None only applies the mask everywhere and doesn't touch the # highest bit # 0x6d: 0b0110_1101 (m) --> 0x0d: 0b0000_1101 (\r) # 0x5e: 0b0101_1110 (^) --> 0x0e: 0b0000_1110 # 0x25: 0b0010_0101 (%) --> 0x05: 0b0000_0101 # 0x25: 0b0010_0101 (%) --> 0x05: 0b0000_0101 (b"\x6d\x5e\x25\x25", 4, None, b"\r\x0e\x05\x05"), # send_end=False sets highest post-mask bit to 0 for all # 0x6d: 0b0110_1101 (m) --> 0x05: 0b0000_0101 # 0x5e: 0b0101_1110 (^) --> 0x06: 0b0000_0110 # 0x25: 0b0010_0101 (%) --> 0x05: 0b0000_0101 # 0x25: 0b0010_0101 (%) --> 0x05: 0b0000_0101 (b"\x6d\x5e\x25\x25", 4, False, b"\x05\x06\x05\x05"), # send_end=True sets highest bit to 0 except for final byte # 0x6d: 0b0110_1101 (m) --> 0x05: 0b0000_0101 # 0x5e: 0b0101_1110 (^) --> 0x06: 0b0000_0110 # 0x25: 0b0010_0101 (%) --> 0x05: 0b0000_0101 # 0x25: 0b0010_0101 (%) --> 0x0d: 0b0000_1101 (b"\x6d\x5e\x25\x25", 4, True, b"\x05\x06\x05\x0d"), # 0x61: 0b0110_0001 (a) --> 0x21: 0b0010_0001 (!) # 0xb1: 0b1011_0001 (±) --> 0x31: 0b0011_0001 (1) (b"a\xb1", 6, None, b"\x21\x31"), # 0x61: 0b0110_0001 (a) --> 0x01: 0b0000_0001 # 0xb1: 0b1011_0001 (±) --> 0x11: 0b0001_0001 (b"a\xb1", 6, False, b"\x01\x11"), # 0x61: 0b0110_0001 (a) --> 0x01: 0b0000_0001 # 0xb1: 0b1011_0001 (±) --> 0x31: 0b0011_0001 (1) (b"a\xb1", 6, True, b"\x011"), ], ) def test_iter_bytes( data: bytes, data_bits: Optional[int], send_end: bool, want: List[bytes] ) -> None: got = b"".join(common.iter_bytes(data, data_bits=data_bits, send_end=send_end)) assert got == want def test_iter_bytes_with_send_end_requires_data_bits() -> None: with pytest.raises(ValueError): # Need to wrap in list otherwise the iterator is never called. list(common.iter_bytes(b"", data_bits=None, send_end=True)) def test_iter_bytes_raises_on_bad_data_bits() -> None: with pytest.raises(ValueError): list(common.iter_bytes(b"", data_bits=0, send_end=None)) pyvisa-py-0.7.2/pyvisa_py/testsuite/test_highlevel.py000066400000000000000000000013231457232645700231330ustar00rootroot00000000000000"""Test creating a resource manager using PyVISA-Py as a backend. :copyright: 2014-2023 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ from pyvisa.highlevel import list_backends from pyvisa.testsuite import BaseTestCase from pyvisa_py import highlevel class TestPyVisaLibrary(BaseTestCase): """Test generic property of PyVisaLibrary.""" def test_list_backends(self): """Test listing backends.""" assert "py" in list_backends() def test_debug_info(self): """Test generating debug infos for PyVISA-py.""" infos = highlevel.PyVisaLibrary.get_debug_info() for key in ("Version",): assert key in infos pyvisa-py-0.7.2/pyvisa_py/testsuite/test_serial.py000066400000000000000000000022571457232645700224520ustar00rootroot00000000000000"""Test creating a resource manager using PyVISA-Py as a backend. :copyright: 2014-2023 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ import pytest from pyvisa import ResourceManager from pyvisa.testsuite import BaseTestCase class TestSerial(BaseTestCase): """Test generic property of PyVisaLibrary.""" serial = pytest.importorskip("serial", reason="PySerial not installed") def test_serial(self): """Test loop://""" msg = b"Test01234567890" available = ["loop://"] expected = [] exp_missing = [] missing = {} rm = ResourceManager("@py") try: dut = rm.open_resource("ASRLloop://::INSTR") print("opened") dut.timeout = 3000 dut.read_termination = "\r\n" dut.write_termination = "\r\n" dut.write(str(msg)) ret_val = dut.read() if str(msg) == ret_val: expected = ["loop://"] except Exception: exp_missing = ["loop://"] assert sorted(available) == sorted(expected) assert sorted(missing) == sorted(exp_missing) pyvisa-py-0.7.2/pyvisa_py/testsuite/test_sessions.py000066400000000000000000000046061457232645700230410ustar00rootroot00000000000000"""Test loading resources. :copyright: 2014-2023 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ import ctypes from pyvisa.constants import InterfaceType from pyvisa.testsuite import BaseTestCase from pyvisa_py.sessions import Session class TestSessions(BaseTestCase): """Test generic property of PyVisaLibrary.""" def test_sessions(self): available = [d for d, _ in Session.iter_valid_session_classes()] missing = [d for d, _ in Session.iter_session_classes_issues()] expected = [(InterfaceType.tcpip, "INSTR"), (InterfaceType.tcpip, "SOCKET")] exp_missing = [] usbs = [(InterfaceType.usb, "INSTR"), (InterfaceType.usb, "RAW")] try: import usb _ = usb.core.find() expected.extend(usbs) except Exception: exp_missing.extend(usbs) gpibs = [(InterfaceType.gpib, "INSTR"), (InterfaceType.gpib, "INTFC")] try: try: from gpib_ctypes import gpib from gpib_ctypes.Gpib import Gpib from gpib_ctypes.gpib.gpib import _lib as gpib_lib except ImportError: import gpib # noqa from Gpib import Gpib # noqa else: # Add some extra binding not available by default extra_funcs = [ ("ibcac", [ctypes.c_int, ctypes.c_int], ctypes.c_int), ("ibgts", [ctypes.c_int, ctypes.c_int], ctypes.c_int), ("ibpct", [ctypes.c_int], ctypes.c_int), ] for name, argtypes, restype in extra_funcs: libfunction = gpib_lib[name] libfunction.argtypes = argtypes libfunction.restype = restype expected.extend(gpibs) except Exception: exp_missing.extend(gpibs) asrl = (InterfaceType.asrl, "INSTR") try: import serial # noqa expected.append(asrl) except Exception: exp_missing.append(asrl) vicp = (InterfaceType.vicp, "INSTR") try: import pyvicp # noqa expected.append(vicp) except Exception: exp_missing.append(vicp) assert sorted(available) == sorted(expected) assert sorted(missing) == sorted(exp_missing) pyvisa-py-0.7.2/pyvisa_py/usb.py000066400000000000000000000265041457232645700166750ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Serial Session implementation using PyUSB. :copyright: 2014-2020 by PyVISA-py Authors, see AUTHORS for more details. :license: MIT, see LICENSE for more details. """ import errno from typing import Any, List, Tuple, Type, Union from pyvisa import attributes, constants from pyvisa.constants import ResourceAttribute, StatusCode from pyvisa.rname import USBInstr, USBRaw from .common import logger from .sessions import Session, UnknownAttribute try: import usb from .protocols import usbraw, usbtmc, usbutil except ImportError as e: msg = "Please install PyUSB to use this resource type.\n%s" Session.register_unavailable(constants.InterfaceType.usb, "INSTR", msg % e) Session.register_unavailable(constants.InterfaceType.usb, "RAW", msg % e) raise try: _ = usb.core.find() except Exception as e: msg = ( "PyUSB does not seem to be properly installed.\n" "Please refer to PyUSB documentation and \n" "install a suitable backend like \n" "libusb 0.1, libusb 1.0, libusbx, \n" "libusb-win32 or OpenUSB.\n%s" % e ) Session.register_unavailable(constants.InterfaceType.usb, "INSTR", msg) Session.register_unavailable(constants.InterfaceType.usb, "RAW", msg) raise class USBTimeoutException(Exception): """Exception used internally to indicate USB timeout.""" pass class USBSession(Session): """Base class for drivers working with usb devices via usb port using pyUSB.""" # Override parsed to take into account the fact that this class is only used # for a specific kind of resource parsed: Union[USBInstr, USBRaw] #: Class to use when instantiating the interface _intf_cls: Union[Type[usbraw.USBRawDevice], Type[usbtmc.USBTMC]] @staticmethod def list_resources() -> List[str]: """Return list of resources for this type of USB device.""" raise NotImplementedError @classmethod def get_low_level_info(cls) -> str: try: ver = usb.__version__ except AttributeError: ver = "N/A" try: # noinspection PyProtectedMember backend = usb.core.find()._ctx.backend.__class__.__module__.split(".")[-1] except Exception: backend = "N/A" return "via PyUSB (%s). Backend: %s" % (ver, backend) def after_parsing(self) -> None: self.interface = self._intf_cls( int(self.parsed.manufacturer_id, 0), int(self.parsed.model_code, 0), self.parsed.serial_number, ) self.attrs.update( { ResourceAttribute.manufacturer_id: int(self.parsed.manufacturer_id, 0), ResourceAttribute.model_code: int(self.parsed.model_code, 0), ResourceAttribute.usb_serial_number: self.parsed.serial_number, ResourceAttribute.usb_interface_number: int( self.parsed.usb_interface_number ), } ) for name, attr in ( ("SEND_END_EN", ResourceAttribute.send_end_enabled), ("SUPPRESS_END_EN", ResourceAttribute.suppress_end_enabled), ("TERMCHAR", ResourceAttribute.termchar), ("TERMCHAR_EN", ResourceAttribute.termchar_enabled), ): attribute = getattr(constants, "VI_ATTR_" + name) self.attrs[attr] = attributes.AttributesByID[attribute].default # Force setting the timeout to get the proper value self.set_attribute( ResourceAttribute.timeout_value, attributes.AttributesByID[attribute].default, ) def _get_timeout(self, attribute: ResourceAttribute) -> Tuple[int, StatusCode]: if self.interface: if self.interface.timeout == 2**32 - 1: self.timeout = None else: self.timeout = self.interface.timeout / 1000 return super(USBSession, self)._get_timeout(attribute) def _set_timeout(self, attribute: ResourceAttribute, value: int) -> StatusCode: status = super(USBSession, self)._set_timeout(attribute, value) timeout = int(self.timeout * 1000) if self.timeout else 2**32 - 1 timeout = min(timeout, 2**32 - 1) if self.interface: self.interface.timeout = timeout return status def read(self, count: int) -> Tuple[bytes, StatusCode]: """Reads data from device or interface synchronously. Corresponds to viRead function of the VISA library. Parameters ----------- count : int Number of bytes to be read. Returns ------- bytes Data read from the device StatusCode Return value of the library call. """ def _usb_reader(): """Data reader identifying usb timeout exception.""" try: return self.interface.read(count) except usb.USBError as exc: if exc.errno in (errno.ETIMEDOUT, -errno.ETIMEDOUT): raise USBTimeoutException() raise supress_end_en, _ = self.get_attribute(ResourceAttribute.suppress_end_enabled) if supress_end_en: raise ValueError( "VI_ATTR_SUPPRESS_END_EN == True is currently unsupported by pyvisa-py" ) term_char, _ = self.get_attribute(ResourceAttribute.termchar) term_char_en, _ = self.get_attribute(ResourceAttribute.termchar_enabled) return self._read( _usb_reader, count, lambda current: True, # USB always returns a complete message supress_end_en, term_char, term_char_en, USBTimeoutException, ) def write(self, data: bytes) -> Tuple[int, StatusCode]: """Writes data to device or interface synchronously. Corresponds to viWrite function of the VISA library. Parameters ---------- data : bytes Data to be written. Returns ------- int Number of bytes actually transferred StatusCode Return value of the library call. """ send_end, _ = self.get_attribute(ResourceAttribute.send_end_enabled) count = self.interface.write(data) return count, StatusCode.success def close(self): self.interface.close() return StatusCode.success def _get_attribute( self, attribute: constants.ResourceAttribute ) -> Tuple[Any, StatusCode]: """Get the value for a given VISA attribute for this session. Use to implement custom logic for attributes. Parameters ---------- attribute : ResourceAttribute Attribute for which the state query is made Returns ------- Any State of the queried attribute for a specified resource StatusCode Return value of the library call. """ raise UnknownAttribute(attribute) def _set_attribute( self, attribute: constants.ResourceAttribute, attribute_state: Any ) -> StatusCode: """Sets the state of an attribute. Corresponds to viSetAttribute function of the VISA library. Parameters ---------- attribute : constants.ResourceAttribute Attribute for which the state is to be modified. (Attributes.*) attribute_state : Any The state of the attribute to be set for the specified object. Returns ------- StatusCode Return value of the library call. """ raise UnknownAttribute(attribute) @Session.register(constants.InterfaceType.usb, "INSTR") class USBInstrSession(USBSession): """Class for USBTMC devices.""" # Override parsed to take into account the fact that this class is only used # for a specific kind of resource parsed: USBInstr #: Class to use when instantiating the interface _intf_cls = usbtmc.USBTMC @staticmethod def list_resources() -> List[str]: 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 try: serial = dev.serial_number except (NotImplementedError, ValueError): msg = ( "Found a device whose serial number cannot be read." " The partial VISA resource name is: " + fmt ) logger.warning( msg, { "board": 0, "manufacturer_id": dev.idVendor, "model_code": dev.idProduct, "serial_number": "???", "usb_interface_number": intfc, }, ) continue out.append( fmt % { "board": 0, "manufacturer_id": dev.idVendor, "model_code": dev.idProduct, "serial_number": serial, "usb_interface_number": intfc, } ) return out @Session.register(constants.InterfaceType.usb, "RAW") class USBRawSession(USBSession): """Class for RAW devices.""" # Override parsed to take into account the fact that this class is only used # for a specific kind of resource parsed: USBRaw #: Class to use when instantiating the interface _intf_cls = usbraw.USBRawDevice @staticmethod def list_resources() -> List[str]: 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 try: serial = dev.serial_number except (NotImplementedError, ValueError, usb.USBError): msg = ( "Found a device whose serial number cannot be read." " The partial VISA resource name is: " + fmt ) logger.warning( msg, { "board": 0, "manufacturer_id": dev.idVendor, "model_code": dev.idProduct, "serial_number": "???", "usb_interface_number": intfc, }, ) continue out.append( fmt % { "board": 0, "manufacturer_id": dev.idVendor, "model_code": dev.idProduct, "serial_number": serial, "usb_interface_number": intfc, } ) return out