├── docs ├── requirements.txt ├── source │ ├── _static │ │ └── logo-full.jpg │ ├── index.rst │ ├── installation.rst │ ├── faq.rst │ └── conf.py ├── index.rst ├── make.bat └── Makefile ├── dev-requirements.txt ├── bors.toml ├── MANIFEST.in ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── instrument-communication-issue.md ├── FUNDING.yml ├── workflows │ ├── docs.yml │ ├── ci.yml │ └── release.yml └── PULL_REQUEST_TEMPLATE.md ├── pyvisa_py ├── protocols │ ├── __init__.py │ ├── usbraw.py │ ├── xdrlib.py │ ├── usbutil.py │ └── vxi11.py ├── testsuite │ ├── keysight_assisted_tests │ │ ├── __init__.py │ │ ├── test_resource_manager.py │ │ └── test_tcpip_resources.py │ ├── __init__.py │ ├── test_highlevel.py │ ├── test_serial.py │ ├── test_sessions.py │ └── test_common.py ├── __init__.py ├── attributes.py ├── common.py ├── prologix.py ├── usb.py ├── serial.py └── highlevel.py ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── AUTHORS ├── LICENSE ├── azure-pipelines.yml ├── README.rst ├── pyproject.toml └── CHANGES /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=4 2 | sphinx-rtd-theme>=1 3 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | ruff 2 | pytest 3 | sphinx 4 | sphinx-rtd-theme -------------------------------------------------------------------------------- /docs/source/_static/logo-full.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyvisa/pyvisa-py/HEAD/docs/source/_static/logo-full.jpg -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = ["Check code formatting", "Tests result", "Docs building", "pyvisa.pyvisa-py.keysight-assisted"] 2 | delete-merged-branches = true 3 | timeout_sec = 300 -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README AUTHORS CHANGES LICENSE 2 | recursive-include pyvisa-py * 3 | recursive-include docs * 4 | prune docs/build 5 | global-exclude *.pyc *~ .DS_Store *__pycache__* *.pyo 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" -------------------------------------------------------------------------------- /pyvisa_py/protocols/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Implements protocols on top of lower level libraries to talk to instruments. 3 | 4 | 5 | :copyright: 2014-2024 by PyVISA-py Authors, see AUTHORS for more details. 6 | :license: MIT, see LICENSE for more details. 7 | 8 | """ 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | __pycache__ 3 | *egg-info* 4 | *.pyc 5 | .DS_Store 6 | docs/_build/ 7 | .idea 8 | build/ 9 | dist/ 10 | MANIFEST 11 | .tox 12 | .eggs 13 | # WebDAV file system cache files 14 | .DAV/ 15 | _test/ 16 | .spyproject/ 17 | .mypy_cache/ 18 | .pytest_cache/ 19 | .cache/ 20 | .vscode 21 | .dmypy.json 22 | 23 | # auto-generated version file 24 | version.py -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | # Ruff version. 4 | rev: v0.14.9 5 | hooks: 6 | # Run the linter. 7 | - id: ruff 8 | # Run the formatter. 9 | - id: ruff-format 10 | - repo: https://github.com/pre-commit/mirrors-mypy 11 | rev: 'v1.19.1' # Use the sha / tag you want to point at 12 | hooks: 13 | - id: mypy 14 | additional_dependencies: [numpy, typing_extensions] -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | To Reproduce 13 | -------------- 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Output of `pyvisa-info`** 21 | 22 | -------------------------------------------------------------------------------- /pyvisa_py/testsuite/keysight_assisted_tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Test relying on the Keysight virtual instrument. 3 | 4 | The PyVISA builbot is connected to a fake instrument implemented using the 5 | Keysight Virtual Instrument IO Test software. 6 | 7 | For this part of the testsuite to be run, you need to set the 8 | PYVISA_KEYSIGHT_VIRTUAL_INSTR environment value. 9 | 10 | See pyvisa/testsuite/keysight_assisted_tests/__init__.py for more details. 11 | 12 | :copyright: 2014-2024 by PyVISA-py Authors, see AUTHORS for more details. 13 | :license: MIT, see LICENSE for more details. 14 | 15 | """ 16 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [MatthieuDartiailh] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /pyvisa_py/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Pure Python backend for PyVISA. 3 | 4 | 5 | :copyright: 2014-2024 by PyVISA-py Authors, see AUTHORS for more details. 6 | :license: MIT, see LICENSE for more details. 7 | 8 | """ 9 | 10 | from importlib.metadata import PackageNotFoundError, version 11 | 12 | # We need to import all attributes so that __init_subclass__() is executed once 13 | # (hence the noqa) 14 | from . import attributes # noqa: F401 15 | from .highlevel import PyVisaLibrary 16 | 17 | __version__ = "unknown" 18 | try: 19 | __version__ = version(__name__) 20 | except PackageNotFoundError: 21 | # package is not installed 22 | pass 23 | 24 | WRAPPER_CLASS = PyVisaLibrary 25 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-20.04 11 | tools: 12 | python: "3.9" 13 | 14 | # Build documentation in the docs/source directory with Sphinx 15 | sphinx: 16 | configuration: docs/source/conf.py 17 | 18 | # Enable epub output 19 | formats: 20 | - epub 21 | 22 | # Optionally declare the Python requirements required to build your docs 23 | python: 24 | install: 25 | - requirements: docs/requirements.txt 26 | - method: pip 27 | path: . 28 | -------------------------------------------------------------------------------- /pyvisa_py/testsuite/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import unittest 4 | 5 | # Set the environment variable to use PyVISA-py as backend 6 | os.environ["PYVISA_LIBRARY"] = "@py" 7 | 8 | 9 | def testsuite(): 10 | """A testsuite that has all the pyvisa-py tests.""" 11 | return unittest.TestLoader().discover(os.path.dirname(__file__)) 12 | 13 | 14 | def main(): 15 | """Runs the testsuite as command line application.""" 16 | try: 17 | unittest.main() 18 | except Exception as e: 19 | print("Error: %s" % e) 20 | 21 | 22 | def run() -> unittest.TestResult: 23 | """Run all tests.""" 24 | test_runner = unittest.TextTestRunner() 25 | return test_runner.run(testsuite()) 26 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | pyvisa-py was started by Hernan E. Grecco . 2 | 3 | It is now maintained by Matthieu C. Dartiailh 4 | 5 | 6 | Other contributors, listed alphabetically, are: 7 | 8 | * Alex Forencich 9 | * Alexander Bessman 10 | * Colin Marquardt 11 | * Lance McCulley 12 | * Martin Ritter 13 | * Matthieu C. Dartiailh 14 | * Sebastian Held 15 | * Thomas Kopp <20.kopp@gmail.com> 16 | * Thorsten Liebig 17 | * Tobias Müller 18 | 19 | (If you think that your name belongs here, please let the maintainer know) 20 | -------------------------------------------------------------------------------- /pyvisa_py/testsuite/test_highlevel.py: -------------------------------------------------------------------------------- 1 | """Test creating a resource manager using PyVISA-Py as a backend. 2 | 3 | 4 | :copyright: 2014-2024 by PyVISA-py Authors, see AUTHORS for more details. 5 | :license: MIT, see LICENSE for more details. 6 | 7 | """ 8 | 9 | from pyvisa.highlevel import list_backends 10 | from pyvisa.testsuite import BaseTestCase 11 | from pyvisa_py import highlevel 12 | 13 | 14 | class TestPyVisaLibrary(BaseTestCase): 15 | """Test generic property of PyVisaLibrary.""" 16 | 17 | def test_list_backends(self): 18 | """Test listing backends.""" 19 | assert "py" in list_backends() 20 | 21 | def test_debug_info(self): 22 | """Test generating debug infos for PyVISA-py.""" 23 | infos = highlevel.PyVisaLibrary.get_debug_info() 24 | for key in ("Version",): 25 | assert key in infos 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/instrument-communication-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Instrument communication issue 3 | about: Template for requesting help communcating with a specific instrument 4 | title: "[COM] Communication issue with XXX using XXX" 5 | labels: instrument 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Instrument details 17 | ------------------ 18 | * Model: 19 | * Communication: TCPIP, GPIB, .... 20 | * Link to the documentation (if available): 21 | 22 | Output of `pyvisa-info` 23 | ----------------------- 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation building 2 | on: 3 | schedule: 4 | - cron: "0 0 * * 2" 5 | push: 6 | branches: 7 | - main 8 | - staging 9 | - trying 10 | pull_request: 11 | branches: 12 | - main 13 | paths: 14 | - .github/workflows/docs.yml 15 | - "pyvisa_py/**" 16 | - "docs/**" 17 | - setup.py 18 | 19 | jobs: 20 | docs: 21 | name: Docs building 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v6 25 | - name: Set up Python 26 | uses: actions/setup-python@v6 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | pip install -r docs/requirements.txt 31 | pip install git+https://github.com/pyvisa/pyvisa.git 32 | - name: Install project 33 | run: | 34 | pip install . 35 | - name: Install graphviz 36 | uses: ts-graphviz/setup-graphviz@v2 37 | - name: Build documentation 38 | run: | 39 | mkdir docs_output; 40 | sphinx-build docs/source docs_output -W -b html; 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014 PyVISA-py Authors and contributors. See AUTHORS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pyvisa_py/attributes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Additional Attributes for specific use with the pyvisa-py package. 3 | 4 | For additional information and VISA attributes see pyvisa.constants 5 | 6 | :copyright: 2014-2024 by PyVISA-py Authors, see AUTHORS for more details. 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | from pyvisa import constants 11 | from pyvisa.attributes import AttrVI_ATTR_TCPIP_KEEPALIVE as former_keepalive 12 | 13 | 14 | class AttrVI_ATTR_TCPIP_KEEPALIVE(former_keepalive): 15 | """Requests that a TCP/IP provider enable the use of keep-alive packets. 16 | 17 | Altering the standard PyVISA attribute to also work on INSTR sessions as 18 | they are using sockets in pyvisa-py as well. 19 | 20 | After the system detects that a connection was dropped, VISA returns a lost 21 | connection error code on subsequent I/O calls on the session. The time required 22 | for the system to detect that the connection was dropped is dependent on the 23 | system and is not settable. 24 | 25 | """ 26 | 27 | resources = [ 28 | (constants.InterfaceType.tcpip, "SOCKET"), 29 | (constants.InterfaceType.tcpip, "INSTR"), 30 | (constants.InterfaceType.vicp, "INSTR"), 31 | ] 32 | -------------------------------------------------------------------------------- /pyvisa_py/testsuite/test_serial.py: -------------------------------------------------------------------------------- 1 | """Test creating a resource manager using PyVISA-Py as a backend. 2 | 3 | 4 | :copyright: 2014-2024 by PyVISA-py Authors, see AUTHORS for more details. 5 | :license: MIT, see LICENSE for more details. 6 | 7 | """ 8 | 9 | import pytest 10 | 11 | from pyvisa import ResourceManager 12 | from pyvisa.testsuite import BaseTestCase 13 | 14 | 15 | class TestSerial(BaseTestCase): 16 | """Test generic property of PyVisaLibrary.""" 17 | 18 | serial = pytest.importorskip("serial", reason="PySerial not installed") 19 | 20 | def test_serial(self): 21 | """Test loop://""" 22 | msg = b"Test01234567890" 23 | 24 | available = ["loop://"] 25 | expected = [] 26 | exp_missing = [] 27 | missing = {} 28 | 29 | rm = ResourceManager("@py") 30 | try: 31 | dut = rm.open_resource("ASRLloop://::INSTR") 32 | print("opened") 33 | dut.timeout = 3000 34 | dut.read_termination = "\r\n" 35 | dut.write_termination = "\r\n" 36 | dut.write(str(msg)) 37 | ret_val = dut.read() 38 | if str(msg) == ret_val: 39 | expected = ["loop://"] 40 | 41 | except Exception: 42 | exp_missing = ["loop://"] 43 | 44 | assert sorted(available) == sorted(expected) 45 | assert sorted(missing) == sorted(exp_missing) 46 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 29 | 30 | - [ ] Closes # (insert issue number if relevant) 31 | - [ ] Executed ``black . && isort -c . && flake8`` with no errors 32 | - [ ] The change is fully covered by automated unit tests 33 | - [ ] Documented in docs/ as appropriate 34 | - [ ] Added an entry to the CHANGES file 35 | -------------------------------------------------------------------------------- /pyvisa_py/testsuite/keysight_assisted_tests/test_resource_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Test the Resource manager.""" 3 | 4 | import pytest 5 | 6 | from pyvisa.rname import ResourceName 7 | from pyvisa.testsuite.keysight_assisted_tests import ( 8 | RESOURCE_ADDRESSES, 9 | copy_func, 10 | require_virtual_instr, 11 | ) 12 | from pyvisa.testsuite.keysight_assisted_tests.test_resource_manager import ( 13 | TestResourceManager as BaseTestResourceManager, 14 | TestResourceParsing as BaseTestResourceParsing, 15 | ) 16 | 17 | 18 | @require_virtual_instr 19 | class TestPyResourceManager(BaseTestResourceManager): 20 | """ """ 21 | 22 | def test_list_resource(self): 23 | """Test listing the available resources. 24 | The bot supports only TCPIP and of those resources we expect to be able 25 | to list only INSTR resources not SOCKET. 26 | """ 27 | # Default settings 28 | resources = self.rm.list_resources() 29 | for v in (v for v in RESOURCE_ADDRESSES.values() if v.endswith("INSTR")): 30 | assert str(ResourceName.from_string(v)) in resources 31 | 32 | test_last_status = pytest.mark.xfail( 33 | copy_func(BaseTestResourceManager.test_last_status) 34 | ) 35 | 36 | test_opening_resource_with_lock = pytest.mark.xfail( 37 | copy_func(BaseTestResourceManager.test_opening_resource_with_lock) 38 | ) 39 | 40 | 41 | @require_virtual_instr 42 | class TestPyResourceParsing(BaseTestResourceParsing): 43 | """ """ 44 | 45 | pass 46 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | 4 | PyVISA-py: Pure Python backend for PyVISA 5 | ========================================= 6 | 7 | .. image:: _static/logo-full.jpg 8 | :alt: PyVISA 9 | 10 | 11 | PyVISA-py is a backend for PyVISA_. It implements most of the methods 12 | for Message Based communication (Serial/USB/GPIB/Ethernet) using Python 13 | and some well developed, easy to deploy and cross platform libraries. 14 | 15 | You can select the PyVISA-py backend using **@py** when instantiating the 16 | visa Resource Manager: 17 | 18 | >>> import pyvisa 19 | >>> rm = pyvisa.ResourceManager('@py') 20 | >>> rm.list_resources() 21 | ('USB0::0x1AB1::0x0588::DS1K00005888::INSTR') 22 | >>> inst = rm.open_resource('USB0::0x1AB1::0x0588::DS1K00005888::INSTR') 23 | >>> print(inst.query("*IDN?")) 24 | 25 | 26 | That's all! Except for **@py**, the code is exactly what you would write to 27 | using the NI-VISA backend for PyVISA. 28 | 29 | Currently Pyvisa-py support the following resources: 30 | 31 | - TCPIP INSTR 32 | - TCPIP SOCKET 33 | - GPIB INSTR 34 | - ASRL INSTR 35 | - USB INSTR 36 | - USB RAW 37 | 38 | Note: 39 | ASRL INSTR supports also URL Handlers like 40 | 41 | - loop:// --> ASLRloop://::INSTR 42 | - socket:// --> ASRLsocket://::INSTR 43 | 44 | These entries will not be listed during the device discovery `rm.list_resources()`. 45 | For further details see https://pyserial.readthedocs.io/en/latest/url_handlers.html 46 | 47 | 48 | You can report a problem or ask for features in the `issue tracker`_. 49 | Or get the code in GitHub_. 50 | 51 | .. toctree:: 52 | :maxdepth: 2 53 | 54 | Installation 55 | FAQ 56 | 57 | .. _PyVISA: http://pyvisa.readthedocs.org/ 58 | .. _GitHub: https://github.com/pyvisa/pyvisa-py 59 | .. _`issue tracker`: https://github.com/pyvisa/pyvisa-py/issues 60 | 61 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Python package 2 | # Create and test a Python package on multiple Python versions. 3 | # Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/python 5 | 6 | trigger: 7 | branches: 8 | include: 9 | - main 10 | - staging 11 | - trying 12 | 13 | pr: 14 | - main 15 | 16 | variables: 17 | PYVISA_KEYSIGHT_VIRTUAL_INSTR: 1 18 | 19 | pool: 20 | name: Keysight-based 21 | demands: KEYSIGHT -equals TCPIP 22 | 23 | steps: 24 | - script: | 25 | export PATH="$HOME/miniconda3/bin:$PATH" 26 | echo Create environment 27 | conda create -n test_ python=3.9 numpy --yes 28 | displayName: "Create environment" 29 | 30 | - script: | 31 | export PATH="$HOME/miniconda3/bin:$PATH" 32 | source $HOME/miniconda3/bin/activate 33 | echo Activate environment 34 | call conda activate test_ 35 | echo Install project and required dependencies 36 | pip install git+https://github.com/pyvisa/pyvisa.git#egg=pyvisa 37 | pip install -e .[serial] 38 | 39 | displayName: "Install dependencies" 40 | 41 | - script: | 42 | export PATH="$HOME/miniconda3/bin:$PATH" 43 | source $HOME/miniconda3/bin/activate 44 | echo Activate environment 45 | call conda activate test_ 46 | echo Install pytest and co 47 | pip install pytest pytest-azurepipelines pytest-cov 48 | echo Run pytest 49 | python -X dev -m pytest --pyargs pyvisa_py --cov pyvisa_py --cov-report xml -v 50 | displayName: "Run tests" 51 | 52 | - script: | 53 | export PATH="$HOME/miniconda3/bin:$PATH" 54 | source $HOME/miniconda3/bin/activate 55 | echo Activate environment 56 | call conda activate test_ 57 | echo Install codecov 58 | pip install codecov 59 | echo Run codecov 60 | codecov --file coverage.xml --token $(CODECOV_TOKEN) --env PYVISA_KEYSIGHT_VIRTUAL_INSTR --tries 5 --required -F unittest --name codecov-umbrella 61 | displayName: "Upload test coverage results" 62 | 63 | - script: | 64 | export PATH="$HOME/miniconda3/bin:$PATH" 65 | conda remove -n test_ --all --yes 66 | displayName: "Remove test environment" 67 | condition: always() 68 | -------------------------------------------------------------------------------- /pyvisa_py/protocols/usbraw.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Implements Session to control USB Raw devices 3 | 4 | Loosely based on PyUSBTMC:python module to handle 5 | USB-TMC(Test and Measurement class) devices. by Noboru Yamamot, Accl. Lab, KEK, JAPAN 6 | 7 | This file is an offspring of the Lantz Project. 8 | 9 | :copyright: 2014-2024 by PyVISA-py Authors, see AUTHORS for more details. 10 | :license: MIT, see LICENSE for more details. 11 | 12 | """ 13 | 14 | from .usbtmc import USBRaw as USBRaw 15 | from .usbutil import find_devices, find_interfaces 16 | 17 | 18 | def find_raw_devices( 19 | vendor=None, product=None, serial_number=None, custom_match=None, **kwargs 20 | ): 21 | """Find connected USB RAW devices. See usbutil.find_devices for more info.""" 22 | 23 | def is_usbraw(dev): 24 | if custom_match and not custom_match(dev): 25 | return False 26 | return bool(find_interfaces(dev, bInterfaceClass=0xFF, bInterfaceSubClass=0xFF)) 27 | 28 | return find_devices(vendor, product, serial_number, is_usbraw, **kwargs) 29 | 30 | 31 | class USBRawDevice(USBRaw): 32 | RECV_CHUNK = 1024**2 33 | 34 | find_devices = staticmethod(find_raw_devices) 35 | 36 | def __init__(self, vendor=None, product=None, serial_number=None, **kwargs): 37 | super(USBRawDevice, self).__init__(vendor, product, serial_number, **kwargs) 38 | 39 | if not (self.usb_recv_ep and self.usb_send_ep): 40 | raise ValueError( 41 | "USBRAW device must have both Bulk-In and Bulk-out endpoints." 42 | ) 43 | 44 | def write(self, data): 45 | """Send raw bytes to the instrument. 46 | 47 | :param data: bytes to be sent to the instrument 48 | :type data: bytes 49 | """ 50 | 51 | begin, end, size = 0, 0, len(data) 52 | bytes_sent = 0 53 | 54 | raw_write = super(USBRawDevice, self).write 55 | 56 | while not end > size: 57 | begin = end 58 | end = begin + self.RECV_CHUNK 59 | bytes_sent += raw_write(data[begin:end]) 60 | 61 | return bytes_sent 62 | 63 | def read(self, size): 64 | """Read raw bytes from the instrument. 65 | 66 | :param size: amount of bytes to be sent to the instrument 67 | :type size: integer 68 | :return: received bytes 69 | :return type: bytes 70 | """ 71 | 72 | raw_read = super(USBRawDevice, self).read 73 | 74 | received = bytearray() 75 | 76 | while not len(received) >= size: 77 | resp = raw_read(self.RECV_CHUNK) 78 | 79 | received.extend(resp) 80 | 81 | return bytes(received) 82 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: 3 | schedule: 4 | - cron: "0 0 * * 2" 5 | push: 6 | branches: 7 | - main 8 | - staging 9 | - trying 10 | pull_request: 11 | branches: 12 | - main 13 | paths: 14 | - .github/workflows/ci.yml 15 | - "pyvisa_py/**" 16 | - pyproject.toml 17 | - setup.py 18 | 19 | jobs: 20 | formatting: 21 | name: Check code formatting 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v6 25 | - name: Set up Python 26 | uses: actions/setup-python@v6 27 | with: 28 | python-version: "3.10" 29 | - name: Install tools 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install ruff mypy pytest 33 | pip install git+https://github.com/pyvisa/pyvisa.git@main 34 | - name: Formatting 35 | run: | 36 | ruff format pyvisa_py --check; 37 | - name: Linting 38 | if: always() 39 | run: | 40 | ruff check pyvisa_py; 41 | - name: Mypy 42 | if: always() 43 | run: | 44 | mypy pyvisa_py; 45 | tests: 46 | name: Unit tests 47 | runs-on: ${{ matrix.os }} 48 | strategy: 49 | matrix: 50 | os: [ubuntu-latest, windows-latest, macos-latest] 51 | python-version: ["3.10", "3.11", "3.12", "3.13-dev"] 52 | steps: 53 | - uses: actions/checkout@v6 54 | - name: Set up Python ${{ matrix.python-version }} 55 | uses: actions/setup-python@v6 56 | with: 57 | python-version: ${{ matrix.python-version }} 58 | - name: Install dependencies 59 | run: | 60 | python -m pip install --upgrade pip 61 | pip install git+https://github.com/pyvisa/pyvisa.git#egg=pyvisa 62 | - name: Install project 63 | run: | 64 | pip install -e . 65 | - name: Test with pytest 66 | run: | 67 | pip install pytest-cov 68 | pytest pyvisa_py/testsuite --cov pyvisa_py --cov-report xml 69 | - name: Upload coverage to Codecov 70 | uses: codecov/codecov-action@v5 71 | with: 72 | token: ${{ secrets.CODECOV_TOKEN }} 73 | flags: unittests 74 | name: codecov-umbrella 75 | fail_ci_if_error: true 76 | 77 | # Added to summarize the matrix (otherwise we would need to list every single 78 | # job in bors.toml) 79 | tests-result: 80 | name: Tests result 81 | if: always() 82 | needs: 83 | - tests 84 | runs-on: ubuntu-latest 85 | steps: 86 | - name: Mark the job as a success 87 | if: needs.tests.result == 'success' 88 | run: exit 0 89 | - name: Mark the job as a failure 90 | if: needs.tests.result != 'success' 91 | run: exit 1 92 | -------------------------------------------------------------------------------- /pyvisa_py/testsuite/test_sessions.py: -------------------------------------------------------------------------------- 1 | """Test loading resources. 2 | 3 | 4 | :copyright: 2014-2024 by PyVISA-py Authors, see AUTHORS for more details. 5 | :license: MIT, see LICENSE for more details. 6 | 7 | """ 8 | 9 | import ctypes 10 | 11 | from pyvisa.constants import InterfaceType 12 | from pyvisa.testsuite import BaseTestCase 13 | from pyvisa_py.sessions import Session 14 | 15 | 16 | class TestSessions(BaseTestCase): 17 | """Test generic property of PyVisaLibrary.""" 18 | 19 | def test_sessions(self): 20 | available = [d for d, _ in Session.iter_valid_session_classes()] 21 | missing = [d for d, _ in Session.iter_session_classes_issues()] 22 | 23 | expected = [ 24 | (InterfaceType.tcpip, "INSTR"), 25 | (InterfaceType.tcpip, "SOCKET"), 26 | (InterfaceType.prlgx_tcpip, "INTFC"), 27 | (InterfaceType.gpib, "INSTR"), 28 | ] 29 | exp_missing = [] 30 | usbs = [(InterfaceType.usb, "INSTR"), (InterfaceType.usb, "RAW")] 31 | try: 32 | import usb 33 | 34 | _ = usb.core.find() 35 | 36 | expected.extend(usbs) 37 | except Exception: 38 | exp_missing.extend(usbs) 39 | 40 | gpibs = [(InterfaceType.gpib, "INTFC")] 41 | try: 42 | try: 43 | from gpib_ctypes import gpib 44 | from gpib_ctypes.Gpib import Gpib 45 | from gpib_ctypes.gpib.gpib import _lib as gpib_lib 46 | except ImportError: 47 | import gpib # noqa 48 | from Gpib import Gpib # noqa 49 | else: 50 | # Add some extra binding not available by default 51 | extra_funcs = [ 52 | ("ibcac", [ctypes.c_int, ctypes.c_int], ctypes.c_int), 53 | ("ibgts", [ctypes.c_int, ctypes.c_int], ctypes.c_int), 54 | ("ibpct", [ctypes.c_int], ctypes.c_int), 55 | ] 56 | for name, argtypes, restype in extra_funcs: 57 | libfunction = gpib_lib[name] 58 | libfunction.argtypes = argtypes 59 | libfunction.restype = restype 60 | 61 | expected.extend(gpibs) 62 | except Exception: 63 | exp_missing.extend(gpibs) 64 | 65 | asrl = (InterfaceType.asrl, "INSTR") 66 | prlgx_aslr = (InterfaceType.prlgx_asrl, "INTFC") 67 | try: 68 | import serial # noqa 69 | 70 | expected.append(asrl) 71 | expected.append(prlgx_aslr) 72 | except Exception: 73 | exp_missing.append(asrl) 74 | exp_missing.append(prlgx_aslr) 75 | 76 | vicp = (InterfaceType.vicp, "INSTR") 77 | try: 78 | import pyvicp # noqa 79 | 80 | expected.append(vicp) 81 | except Exception: 82 | exp_missing.append(vicp) 83 | 84 | print(available, missing) 85 | assert sorted(available) == sorted(expected) 86 | assert sorted(missing) == sorted(exp_missing) 87 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | PyVISA-py 2 | ========= 3 | 4 | .. image:: https://github.com/pyvisa/pyvisa-py/workflows/Continuous%20Integration/badge.svg 5 | :target: https://github.com/pyvisa/pyvisa-py/actions 6 | :alt: Continuous integration 7 | .. image:: https://github.com/pyvisa/pyvisa-py/workflows/Documentation%20building/badge.svg 8 | :target: https://github.com/pyvisa/pyvisa-py/actions 9 | :alt: Documentation building 10 | .. image:: https://dev.azure.com/pyvisa/pyvisa-py/_apis/build/status/pyvisa.pyvisa-py.keysight-assisted?branchName=main 11 | :target: https://dev.azure.com/pyvisa/pyvisa-py/_build 12 | :alt: Keysight assisted testing 13 | .. image:: https://codecov.io/gh/pyvisa/pyvisa-py/branch/main/graph/badge.svg 14 | :target: https://codecov.io/gh/pyvisa/pyvisa-py 15 | :alt: Code Coverage 16 | .. image:: https://readthedocs.org/projects/pyvisa-py/badge/?version=latest 17 | :target: https://pyvisa.readthedocs.io/en/latest/?badge=latest 18 | :alt: Documentation Status 19 | .. image:: https://img.shields.io/pypi/l/PyVISA-py 20 | :target: https://pypi.python.org/pypi/pyvisa-py 21 | :alt: PyPI - License 22 | .. image:: https://img.shields.io/pypi/v/PyVISA-py 23 | :target: https://pypi.python.org/pypi/pyvisa-py 24 | :alt: PyPI 25 | 26 | A PyVISA backend that implements a large part of the "Virtual Instrument Software 27 | Architecture" (VISA_) in pure Python (with the help of some nice cross platform 28 | libraries python packages!). 29 | 30 | Description 31 | ----------- 32 | 33 | PyVISA started as wrapper for the IVI-VISA library and therefore you need to install 34 | a VISA library in your system (National Instruments, Keysight, etc). This works 35 | most of the time, for most people. But IVI-VISA implementations are proprietary 36 | libraries that only works on certain systems. That is when PyVISA-py jumps in. 37 | 38 | Starting from version 1.6, PyVISA allows to use different backends. These 39 | backends can be dynamically loaded. PyVISA-py is one of such backends. It 40 | implements most of the methods for Message Based communication 41 | (Serial/USB/GPIB/Ethernet) using Python and some well developed, easy to deploy 42 | and cross platform libraries 43 | 44 | .. _VISA: https://www.ivifoundation.org/specifications/default.html#visa-specifications 45 | 46 | 47 | VISA and Python 48 | --------------- 49 | 50 | Python has a couple of features that make it very interesting for measurement 51 | controlling: 52 | 53 | - Python is an easy-to-learn scripting language with short development cycles. 54 | - It represents a high abstraction level, which perfectly blends with the 55 | abstraction level of measurement programs. 56 | - It has a very rich set of native libraries, including numerical and plotting 57 | modules for data analysis and visualisation. 58 | - A large set of books (in many languages) and on-line publications is available. 59 | 60 | 61 | Requirements 62 | ------------ 63 | 64 | - Python 3 65 | - PyVISA 66 | 67 | Optionally: 68 | 69 | - PySerial (to interface with Serial instruments) 70 | - PyUSB (to interface with USB instruments) 71 | - linux-gpib (to interface with gpib instruments, only on linux) 72 | - gpib-ctypes (to interface with GPIB instruments on Windows and Linux) 73 | - psutil (to discover TCPIP devices across multiple interfaces) 74 | - zeroconf (for HiSLIP and VICP devices discovery) 75 | - pyvicp (to enable the Teledyne LeCroy proprietary VICP protocol) 76 | 77 | Please refer to `pyproject.toml <./pyproject.toml>`_ for the specific version 78 | requirements. 79 | 80 | Installation 81 | -------------- 82 | 83 | Using pip:: 84 | 85 | $ pip install pyvisa-py 86 | 87 | 88 | Documentation 89 | -------------- 90 | 91 | The documentation can be read online at https://pyvisa-py.readthedocs.org 92 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload wheels 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '0 0 * * 3' 6 | push: 7 | tags: 8 | - '*' 9 | 10 | jobs: 11 | build_sdist: 12 | name: Build sdist 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v6 17 | - name: Get history and tags for SCM versioning to work 18 | run: | 19 | git fetch --prune --unshallow 20 | git fetch --depth=1 origin +refs/tags/*:refs/tags/* 21 | - name: Setup Python 22 | uses: actions/setup-python@v6 23 | - name: Build sdist 24 | run: | 25 | pip install --upgrade pip 26 | pip install wheel build 27 | python -m build . -s 28 | - name: Test sdist 29 | run: | 30 | pip install pytest 31 | pip install dist/*.tar.gz 32 | python -X dev -m pytest --pyargs pyvisa_py 33 | - name: Store artifacts 34 | uses: actions/upload-artifact@v6 35 | with: 36 | name: cibw-sdist 37 | path: dist/* 38 | 39 | build_wheel: 40 | name: Build wheel 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v6 45 | - name: Get history and tags for SCM versioning to work 46 | run: | 47 | git fetch --prune --unshallow 48 | git fetch --depth=1 origin +refs/tags/*:refs/tags/* 49 | - name: Setup Python 50 | uses: actions/setup-python@v6 51 | - name: Build wheels 52 | run: | 53 | pip install --upgrade pip 54 | pip install wheel build 55 | python -m build . -w 56 | - name: Test wheel 57 | run: | 58 | pip install pytest 59 | pip install dist/*.whl 60 | python -X dev -m pytest --pyargs pyvisa_py 61 | - name: Store artifacts 62 | uses: actions/upload-artifact@v6 63 | with: 64 | name: cibw-wheel 65 | path: dist/* 66 | 67 | publish: 68 | if: github.event_name == 'push' 69 | needs: [build_wheel, build_sdist] 70 | runs-on: ubuntu-latest 71 | environment: 72 | name: pypi 73 | url: https://pypi.org/p/pyvisa-py/ 74 | permissions: 75 | id-token: write 76 | steps: 77 | - name: Download all the dists 78 | uses: actions/download-artifact@v7.0.0 79 | with: 80 | pattern: cibw-* 81 | path: dist 82 | merge-multiple: true 83 | 84 | - uses: pypa/gh-action-pypi-publish@release/v1 85 | 86 | github-release: 87 | name: >- 88 | Sign the Python 🐍 distribution 📦 with Sigstore 89 | and create a GitHub Release 90 | runs-on: ubuntu-latest 91 | needs: 92 | - publish 93 | 94 | permissions: 95 | contents: write 96 | id-token: write 97 | 98 | steps: 99 | - name: Download all the dists 100 | uses: actions/download-artifact@v7.0.0 101 | with: 102 | pattern: cibw-* 103 | path: dist 104 | merge-multiple: true 105 | - name: Sign the dists with Sigstore 106 | uses: sigstore/gh-action-sigstore-python@v3.2.0 107 | with: 108 | inputs: >- 109 | ./dist/*.tar.gz 110 | ./dist/*.whl 111 | - name: Create GitHub Release 112 | env: 113 | GITHUB_TOKEN: ${{ github.token }} 114 | run: >- 115 | gh release create 116 | '${{ github.ref_name }}' 117 | --repo '${{ github.repository }}' 118 | --generate-notes 119 | - name: Upload artifact signatures to GitHub Release 120 | env: 121 | GITHUB_TOKEN: ${{ github.token }} 122 | run: >- 123 | gh release upload 124 | '${{ github.ref_name }}' dist/** 125 | --repo '${{ github.repository }}' 126 | -------------------------------------------------------------------------------- /pyvisa_py/common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Common code. 3 | 4 | :copyright: 2014-2024 by PyVISA-sim Authors, see AUTHORS for more details. 5 | :license: MIT, see LICENSE for more details. 6 | 7 | """ 8 | 9 | import logging 10 | from typing import Iterator, Optional 11 | 12 | from pyvisa import logger 13 | 14 | LOGGER = logging.LoggerAdapter(logger, {"backend": "py"}) # type: ignore 15 | 16 | 17 | class NamedObject(object): 18 | """A class to construct named sentinels.""" 19 | 20 | #: Name used to identify the sentinel 21 | name: str 22 | 23 | def __init__(self, name) -> None: 24 | self.name = name 25 | 26 | def __repr__(self) -> str: 27 | return "<%s>" % self.name 28 | 29 | __str__ = __repr__ 30 | 31 | 32 | def int_to_byte(val): 33 | return val.to_bytes(1, "big") 34 | 35 | 36 | # TODO(anyone): This is copypasta from `pyvisa-sim` project - find a way to 37 | # reduce duplication, probably in that project instead of here. 38 | def _create_bitmask(bits: int) -> int: 39 | """Create a bitmask for the given number of bits.""" 40 | mask = (1 << bits) - 1 41 | return mask 42 | 43 | 44 | # TODO(anyone): This is copypasta from `pyvisa-sim` project - find a way to 45 | # reduce duplication, probably in that project instead of here. 46 | def iter_bytes( 47 | data: bytes, data_bits: Optional[int] = None, send_end: Optional[bool] = None 48 | ) -> Iterator[bytes]: 49 | """Clip values to the correct number of bits per byte. 50 | Serial communication may use from 5 to 8 bits. 51 | Parameters 52 | ---------- 53 | data : The data to clip as a byte string. 54 | data_bits : How many bits per byte should be sent. Clip to this many bits. 55 | For example: data_bits=5: 0xff (0b1111_1111) --> 0x1f (0b0001_1111). 56 | Acceptable range is 5 to 8, inclusive. Values above 8 will be clipped to 8. 57 | This maps to the VISA attribute VI_ATTR_ASRL_DATA_BITS. 58 | send_end : 59 | If None (the default), apply the mask that is determined by data_bits. 60 | If False, apply the mask and set the highest (post-mask) bit to 0 for 61 | all bytes. 62 | If True, apply the mask and set the highest (post-mask) bit to 0 for 63 | all bytes except for the final byte, which has the highest bit set to 1. 64 | References 65 | ---------- 66 | + https://www.ivifoundation.org/downloads/Architecture%20Specifications/vpp43_2022-05-19.pdf, 67 | + https://www.ni.com/docs/en-US/bundle/ni-visa/page/ni-visa/vi_attr_asrl_data_bits.html, 68 | + https://www.ni.com/docs/en-US/bundle/ni-visa/page/ni-visa/vi_attr_asrl_end_out.html 69 | 70 | """ 71 | if send_end and data_bits is None: 72 | raise ValueError("'send_end' requires a valid 'data_bits' value.") 73 | 74 | if data_bits is None: 75 | for d in data: 76 | yield bytes([d]) 77 | else: 78 | if data_bits <= 0: 79 | raise ValueError("'data_bits' cannot be zero or negative") 80 | if data_bits > 8: 81 | data_bits = 8 82 | 83 | if send_end is None: 84 | # only apply the mask 85 | mask = _create_bitmask(data_bits) 86 | for d in data: 87 | yield bytes([d & mask]) 88 | elif bool(send_end) is False: 89 | # apply the mask and set highest bits to 0 90 | # This is effectively the same has reducing the mask by 1 bit. 91 | mask = _create_bitmask(data_bits - 1) 92 | for d in data: 93 | yield bytes([d & mask]) 94 | elif bool(send_end) is True: 95 | # apply the mask and set highest bits to 0 96 | # This is effectively the same has reducing the mask by 1 bit. 97 | mask = _create_bitmask(data_bits - 1) 98 | for d in data[:-1]: 99 | yield bytes([d & mask]) 100 | # except for the last byte which has it's highest bit set to 1. 101 | last_byte = data[-1] 102 | highest_bit = 1 << (data_bits - 1) 103 | yield bytes([(last_byte & mask) | highest_bit]) 104 | else: 105 | raise ValueError(f"Unknown 'send_end' value '{send_end}'") 106 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "PyVISA-py" 3 | description = "Pure Python implementation of a VISA library." 4 | readme = "README.rst" 5 | requires-python = ">=3.10" 6 | license = { file = "LICENSE" } 7 | authors = [{ name = "Hernan E. Grecco", email = "hernan.grecco@gmail.com" }] 8 | maintainers = [{ name = "Matthieu C. Dartiailh", email = "m.dartiailh@gmail.com" }] 9 | keywords = ["VISA", "GPIB", "USB", "serial", "RS232", "measurement", "acquisition"] 10 | classifiers = [ 11 | "Development Status :: 4 - Beta", 12 | "Intended Audience :: Developers", 13 | "Intended Audience :: Science/Research", 14 | "License :: OSI Approved :: MIT License", 15 | "Operating System :: Microsoft :: Windows", 16 | "Operating System :: POSIX :: Linux", 17 | "Operating System :: MacOS :: MacOS X", 18 | "Programming Language :: Python", 19 | "Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator", 20 | "Topic :: Software Development :: Libraries :: Python Modules", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Programming Language :: Python :: 3.12", 24 | "Programming Language :: Python :: 3.13", 25 | ] 26 | dependencies = ["pyvisa>=1.15.0", "typing_extensions"] 27 | dynamic = ["version"] 28 | 29 | [project.optional-dependencies] 30 | gpib-ctypes = ["gpib-ctypes>=0.3.0"] 31 | serial = ["pyserial>=3.0"] 32 | usb = ["pyusb"] 33 | usb-full = ["pyusb", "libusb-package"] 34 | psutil = ["psutil"] 35 | hislip-discovery = ["zeroconf"] 36 | vicp = ["pyvicp", "zeroconf"] 37 | 38 | 39 | [project.urls] 40 | homepage = "https://github.com/pyvisa/pyvisa-py" 41 | documentation = "https://pyvisa-py.readthedocs.io/en/latest/" 42 | repository = "https://github.com/pyvisa/pyvisa-py" 43 | changelog = "https://github.com/pyvisa/pyvisa-py/blob/main/CHANGES" 44 | 45 | [build-system] 46 | requires = ["setuptools>=61.2", "wheel", "setuptools_scm[toml]>=3.4.3"] 47 | build-backend = "setuptools.build_meta" 48 | 49 | [tool.setuptools_scm] 50 | write_to = "pyvisa_py/version.py" 51 | write_to_template = """ 52 | # This file is auto-generated by setuptools-scm do NOT edit it. 53 | 54 | from collections import namedtuple 55 | 56 | #: A namedtuple of the version info for the current release. 57 | _version_info = namedtuple("_version_info", "major minor micro status") 58 | 59 | parts = "{version}".split(".", 3) 60 | version_info = _version_info( 61 | int(parts[0]), 62 | int(parts[1]), 63 | int(parts[2]), 64 | parts[3] if len(parts) == 4 else "", 65 | ) 66 | 67 | # Remove everything but the 'version_info' from this module. 68 | del namedtuple, _version_info, parts 69 | 70 | __version__ = "{version}" 71 | """ 72 | 73 | [tool.ruff] 74 | src = ["src"] 75 | extend-exclude = ["pyvisa/thirdparty/*"] 76 | line-length = 88 77 | 78 | [tool.ruff.lint] 79 | select = ["C", "E", "F", "W", "I", "C90", "RUF"] 80 | extend-ignore = ["E501", "RUF012"] 81 | 82 | [tool.ruff.lint.isort] 83 | combine-as-imports = true 84 | known-first-party = ["pyvisa"] 85 | 86 | [tool.ruff.lint.mccabe] 87 | max-complexity = 20 88 | 89 | [tool.pytest.ini_options] 90 | minversion = "6.0" 91 | 92 | [tool.mypy] 93 | follow_imports = "normal" 94 | strict_optional = true 95 | 96 | [[tool.mypy.overrides]] 97 | module = ["usb.*", "libusb_package.*", "serial.*", "gpib.*", "Gpib.*", "gpib_ctypes.*", "pyvicp.*"] 98 | ignore_missing_imports = true 99 | 100 | [tool.coverage] 101 | [tool.coverage.run] 102 | branch = true 103 | source = ["pyvisa_py"] 104 | 105 | [tool.coverage.report] 106 | # Regexes for lines to exclude from consideration 107 | exclude_lines = [ 108 | # Have to re-enable the standard pragma 109 | "pragma: no cover", 110 | 111 | # Don't complain if tests don't hit defensive assertion code: 112 | "raise NotImplementedError", 113 | "pass", 114 | 115 | # Don't complain about abstract methods, they aren't run: 116 | "@(abc\\.)?abstractmethod", 117 | 118 | # Don't complain about type checking 119 | "if TYPE_CHECKING:", 120 | 121 | # Don't complain about ellipsis in overload 122 | "\\.\\.\\.", 123 | ] 124 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | 4 | PyVISA-py: Pure Python backend for PyVISA 5 | ========================================= 6 | 7 | .. image:: _static/logo-full.jpg 8 | :alt: PyVISA 9 | 10 | 11 | PyVISA-py is a backend for PyVISA_. It implements most of the methods 12 | for Message Based communication (Serial/USB/GPIB/Ethernet) using Python 13 | and some well developed, easy to deploy and cross platform libraries. 14 | 15 | You can select the PyVISA-py backend using **@py** when instantiating the 16 | visa Resource Manager: 17 | 18 | >>> import pyvisa 19 | >>> rm = pyvisa.ResourceManager('@py') 20 | >>> rm.list_resources() 21 | ('USB0::0x1AB1::0x0588::DS1K00005888::INSTR') 22 | >>> inst = rm.open_resource('USB0::0x1AB1::0x0588::DS1K00005888::INSTR') 23 | >>> print(inst.query("*IDN?")) 24 | 25 | 26 | That's all! Except for **@py**, the code is exactly what you would write to 27 | using the NI-VISA backend for PyVISA. 28 | 29 | 30 | Installation 31 | ============ 32 | 33 | Just run the following command in your console: 34 | 35 | pip install pyvisa-py 36 | 37 | 38 | You can report a problem or ask for features in the `issue tracker`_. 39 | Or get the code in GitHub_. 40 | 41 | 42 | FAQ 43 | === 44 | 45 | 46 | Which libraries are used by PyVISA-py? 47 | -------------------------------------- 48 | 49 | It depends on the interface type. For **ASRL** and **USB** we use PySerial_ and PyUSB_, 50 | respectively. PySerial_ version 3.0 or newer is required. 51 | 52 | For **TCPIP** we use the :py:mod:`socket` module in the Python Standard Library. 53 | 54 | On Linux, **GPIB** resources are supported using the `linux-gpib`_ project's Python bindings. 55 | On Windows as well as Linux systems with proprietary GPIB device drivers, experimental GPIB 56 | support is available through `gpib-ctypes`_. The `gpib-ctypes`_ library is still in 57 | development so please report any issues you may encounter. 58 | 59 | 60 | If I only need **TCPIP**, do I need to install PySerial, PyUSB, linux-gpib, or gpib-ctypes? 61 | ------------------------------------------------------------------------------------------- 62 | 63 | No. Libraries are loaded on demand. 64 | 65 | 66 | How do I know if PyVISA-py is properly installed? 67 | ------------------------------------------------- 68 | 69 | Using the pyvisa information tool. Run in your console:: 70 | 71 | pyvisa-info 72 | 73 | You will get info about PyVISA, the installed backends and their options. 74 | 75 | 76 | Which resource types are supported? 77 | ----------------------------------- 78 | 79 | Now: 80 | 81 | - ASRL INSTR 82 | - USB INSTR 83 | - TCPIP INSTR 84 | - USB RAW 85 | - TCPIP SOCKET 86 | - GPIB INSTR 87 | 88 | 89 | 90 | Are all VISA attributes and methods implemented? 91 | ------------------------------------------------ 92 | 93 | No. We have implemented those attributes and methods that are most commonly 94 | needed. We would like to reach feature parity. If there is something that you 95 | need, let us know. 96 | 97 | 98 | Why are you developing this? 99 | ---------------------------- 100 | 101 | The `National Instruments's VISA`_ is a proprietary library that only works on certain systems. 102 | We wanted to provide a compatible alternative. 103 | 104 | 105 | Why not using LibreVISA? 106 | ------------------------ 107 | 108 | LibreVISA_ is still young. However, you can already use it with the NI backend 109 | as it has the same API. We think that PyVISA-py is easier to hack and we can 110 | quickly reach feature parity with NI-VISA for message-based instruments. 111 | 112 | 113 | Why putting PyVISA in the middle? 114 | --------------------------------- 115 | 116 | Because it allows you to change the backend easily without changing your application. 117 | In other projects we implemented classes to call USBTMC devices without PyVISA. 118 | But this leads to code duplication or an adapter class in your code. 119 | By using PyVISA as a frontend to many backends, we abstract these things 120 | from higher level applications. 121 | 122 | 123 | 124 | .. _PySerial: https://pythonhosted.org/pyserial/ 125 | .. _PyVISA: http://pyvisa.readthedocs.org/ 126 | .. _PyUSB: https://github.com/pyusb/pyusb 127 | .. _PyPI: https://pypi.python.org/pypi/PyVISA-py 128 | .. _GitHub: https://github.com/pyvisa/pyvisa-py 129 | .. _`National Instruments's VISA`: http://ni.com/visa/ 130 | .. _`LibreVISA`: http://www.librevisa.org/ 131 | .. _`issue tracker`: https://github.com/pyvisa/pyvisa-py/issues 132 | .. _`linux-gpib`: http://linux-gpib.sourceforge.net/ 133 | .. _`gpib-ctypes`: https://pypi.org/project/gpib-ctypes/ 134 | 135 | -------------------------------------------------------------------------------- /pyvisa_py/testsuite/test_common.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | import pytest 4 | 5 | from pyvisa_py import common 6 | 7 | 8 | # TODO(anyone): This is copypasta from `pyvisa-sim` project - find a way to 9 | # reduce duplication, probably in that project instead of here. 10 | @pytest.mark.parametrize( 11 | "bits, want", 12 | [ 13 | (0, 0b0), 14 | (1, 0b1), 15 | (5, 0b0001_1111), 16 | (7, 0b0111_1111), 17 | (8, 0b1111_1111), 18 | (11, 0b0111_1111_1111), 19 | ], 20 | ) 21 | def test_create_bitmask(bits, want): 22 | got = common._create_bitmask(bits) 23 | assert got == want 24 | 25 | 26 | # TODO(anyone): This is copypasta from `pyvisa-sim` project - find a way to 27 | # reduce duplication, probably in that project instead of here. 28 | @pytest.mark.parametrize( 29 | "data, data_bits, send_end, want", 30 | [ 31 | (b"\x01", None, False, b"\x01"), 32 | (b"hello world!", None, False, b"hello world!"), 33 | # Only apply the mask 34 | (b"\x03", 2, None, b"\x03"), # 0b0000_0011 --> 0b0000_0011 35 | (b"\x04", 2, None, b"\x00"), # 0b0000_0100 --> 0b0000_0000 36 | (b"\xff", 5, None, b"\x1f"), # 0b1111_1111 --> 0b0001_1111 37 | (b"\xfe", 7, None, b"\x7e"), # 0b1111_1110 --> 0b0111_1110 38 | (b"\xfe", 8, None, b"\xfe"), # 0b1111_1110 --> 0b1111_1110 39 | (b"\xff", 9, None, b"\xff"), # 0b1111_1111 --> 0b1111_1111 40 | # Always set highest bit *of data_bits* to 0 41 | (b"\x04", 2, False, b"\x00"), # 0b0000_0100 --> 0b0000_0000 42 | (b"\x04", 3, False, b"\x00"), # 0b0000_0100 --> 0b0000_0000 43 | (b"\x05", 3, False, b"\x01"), # 0b0000_0101 --> 0b0000_0001 44 | (b"\xff", 7, False, b"\x3f"), # 0b1111_1111 --> 0b0011_1111 45 | (b"\xff", 8, False, b"\x7f"), # 0b1111_1111 --> 0b0111_1111 46 | # Always set highest bit *of data_bits* to 1 47 | (b"\x04", 2, True, b"\x02"), # 0b0000_0100 --> 0b0000_0010 48 | (b"\x04", 3, True, b"\x04"), # 0b0000_0100 --> 0b0000_0100 49 | (b"\x01", 3, True, b"\x05"), # 0b0000_0001 --> 0b0000_0101 50 | (b"\x9f", 7, True, b"\x5f"), # 0b1001_1111 --> 0b0101_1111 51 | (b"\x9f", 8, True, b"\x9f"), # 0b1001_1111 --> 0b1001_1111 52 | # data_bits >8 bits act like data_bits=8, as type(data) is "bytes" 53 | # which is limited 8 bits per character. 54 | (b"\xff", 9, None, b"\xff"), 55 | (b"\xff", 9, False, b"\x7f"), 56 | (b"\xff", 9, True, b"\xff"), 57 | # send_end=None only applies the mask everywhere and doesn't touch the 58 | # highest bit 59 | # 0x6d: 0b0110_1101 (m) --> 0x0d: 0b0000_1101 (\r) 60 | # 0x5e: 0b0101_1110 (^) --> 0x0e: 0b0000_1110 61 | # 0x25: 0b0010_0101 (%) --> 0x05: 0b0000_0101 62 | # 0x25: 0b0010_0101 (%) --> 0x05: 0b0000_0101 63 | (b"\x6d\x5e\x25\x25", 4, None, b"\r\x0e\x05\x05"), 64 | # send_end=False sets highest post-mask bit to 0 for all 65 | # 0x6d: 0b0110_1101 (m) --> 0x05: 0b0000_0101 66 | # 0x5e: 0b0101_1110 (^) --> 0x06: 0b0000_0110 67 | # 0x25: 0b0010_0101 (%) --> 0x05: 0b0000_0101 68 | # 0x25: 0b0010_0101 (%) --> 0x05: 0b0000_0101 69 | (b"\x6d\x5e\x25\x25", 4, False, b"\x05\x06\x05\x05"), 70 | # send_end=True sets highest bit to 0 except for final byte 71 | # 0x6d: 0b0110_1101 (m) --> 0x05: 0b0000_0101 72 | # 0x5e: 0b0101_1110 (^) --> 0x06: 0b0000_0110 73 | # 0x25: 0b0010_0101 (%) --> 0x05: 0b0000_0101 74 | # 0x25: 0b0010_0101 (%) --> 0x0d: 0b0000_1101 75 | (b"\x6d\x5e\x25\x25", 4, True, b"\x05\x06\x05\x0d"), 76 | # 0x61: 0b0110_0001 (a) --> 0x21: 0b0010_0001 (!) 77 | # 0xb1: 0b1011_0001 (±) --> 0x31: 0b0011_0001 (1) 78 | (b"a\xb1", 6, None, b"\x21\x31"), 79 | # 0x61: 0b0110_0001 (a) --> 0x01: 0b0000_0001 80 | # 0xb1: 0b1011_0001 (±) --> 0x11: 0b0001_0001 81 | (b"a\xb1", 6, False, b"\x01\x11"), 82 | # 0x61: 0b0110_0001 (a) --> 0x01: 0b0000_0001 83 | # 0xb1: 0b1011_0001 (±) --> 0x31: 0b0011_0001 (1) 84 | (b"a\xb1", 6, True, b"\x011"), 85 | ], 86 | ) 87 | def test_iter_bytes( 88 | data: bytes, data_bits: Optional[int], send_end: bool, want: List[bytes] 89 | ) -> None: 90 | got = b"".join(common.iter_bytes(data, data_bits=data_bits, send_end=send_end)) 91 | assert got == want 92 | 93 | 94 | def test_iter_bytes_with_send_end_requires_data_bits() -> None: 95 | with pytest.raises(ValueError): 96 | # Need to wrap in list otherwise the iterator is never called. 97 | list(common.iter_bytes(b"", data_bits=None, send_end=True)) 98 | 99 | 100 | def test_iter_bytes_raises_on_bad_data_bits() -> None: 101 | with pytest.raises(ValueError): 102 | list(common.iter_bytes(b"", data_bits=0, send_end=None)) 103 | -------------------------------------------------------------------------------- /pyvisa_py/testsuite/keysight_assisted_tests/test_tcpip_resources.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Test the TCPIP based resources.""" 3 | 4 | import socket 5 | 6 | import pytest 7 | 8 | from pyvisa.constants import ResourceAttribute 9 | from pyvisa.testsuite.keysight_assisted_tests import copy_func, require_virtual_instr 10 | from pyvisa.testsuite.keysight_assisted_tests.test_tcpip_resources import ( 11 | TestTCPIPInstr as TCPIPInstrBaseTest, 12 | TestTCPIPSocket as TCPIPSocketBaseTest, 13 | ) 14 | 15 | 16 | @require_virtual_instr 17 | class TestTCPIPInstr(TCPIPInstrBaseTest): 18 | """Test pyvisa-py against a TCPIP INSTR resource.""" 19 | 20 | #: Type of resource being tested in this test case. 21 | #: See RESOURCE_ADDRESSES in the __init__.py file of this package for 22 | #: acceptable values 23 | RESOURCE_TYPE = "TCPIP::INSTR" 24 | 25 | #: Minimal timeout value accepted by the resource. When setting the timeout 26 | #: to VI_TMO_IMMEDIATE, Visa (Keysight at least) may actually use a 27 | #: different value depending on the values supported by the resource. 28 | MINIMAL_TIMEOUT = 0 # XXX should we try to have this match VISA ? 29 | 30 | # XXX Skip test clear to see if it has some bad side effect 31 | test_clear = pytest.mark.skip(copy_func(TCPIPInstrBaseTest.test_clear)) 32 | 33 | test_wrapping_handler = pytest.mark.xfail( 34 | copy_func(TCPIPInstrBaseTest.test_wrapping_handler) 35 | ) 36 | 37 | test_managing_visa_handler = pytest.mark.xfail( 38 | copy_func(TCPIPInstrBaseTest.test_managing_visa_handler) 39 | ) 40 | 41 | test_wait_on_event = pytest.mark.xfail( 42 | copy_func(TCPIPInstrBaseTest.test_wait_on_event) 43 | ) 44 | 45 | test_wait_on_event_timeout = pytest.mark.xfail( 46 | copy_func(TCPIPInstrBaseTest.test_wait_on_event_timeout) 47 | ) 48 | 49 | test_getting_unknown_buffer = pytest.mark.xfail( 50 | copy_func(TCPIPInstrBaseTest.test_getting_unknown_buffer) 51 | ) 52 | 53 | test_manual_async_read = pytest.mark.xfail( 54 | copy_func(TCPIPInstrBaseTest.test_manual_async_read) 55 | ) 56 | 57 | test_uninstall_all_handlers = pytest.mark.xfail( 58 | copy_func(TCPIPInstrBaseTest.test_uninstall_all_handlers) 59 | ) 60 | 61 | test_handler_clean_up_on_resource_del = pytest.mark.xfail( 62 | copy_func(TCPIPInstrBaseTest.test_handler_clean_up_on_resource_del) 63 | ) 64 | 65 | test_uninstalling_missing_visa_handler = pytest.mark.xfail( 66 | copy_func(TCPIPInstrBaseTest.test_uninstalling_missing_visa_handler) 67 | ) 68 | 69 | test_handling_invalid_handler = pytest.mark.xfail( 70 | copy_func(TCPIPInstrBaseTest.test_handling_invalid_handler) 71 | ) 72 | 73 | test_write_raw_read_bytes = pytest.mark.xfail( 74 | copy_func(TCPIPInstrBaseTest.test_write_raw_read_bytes) 75 | ) 76 | 77 | test_io_prot_attr = pytest.mark.xfail( 78 | copy_func(TCPIPInstrBaseTest.test_io_prot_attr) 79 | ) 80 | 81 | test_shared_locking = pytest.mark.xfail( 82 | copy_func(TCPIPInstrBaseTest.test_shared_locking) 83 | ) 84 | 85 | test_timeout = pytest.mark.xfail(copy_func(TCPIPInstrBaseTest.test_timeout)) 86 | 87 | test_attribute_handling = pytest.mark.xfail( 88 | copy_func(TCPIPInstrBaseTest.test_attribute_handling) 89 | ) 90 | 91 | def test_keepalive_attribute_vxi11(self): 92 | assert self.instr.visalib.sessions[self.instr.session].keepalive is False 93 | self.instr.set_visa_attribute(ResourceAttribute.tcpip_keepalive, True) 94 | assert self.instr.visalib.sessions[self.instr.session].keepalive is True 95 | assert ( 96 | self.instr.visalib.sessions[self.instr.session].interface.sock.getsockopt( 97 | socket.SOL_SOCKET, socket.SO_KEEPALIVE 98 | ) 99 | == 1 100 | ) 101 | 102 | self.instr.set_visa_attribute(ResourceAttribute.tcpip_keepalive, False) 103 | assert self.instr.visalib.sessions[self.instr.session].keepalive is False 104 | assert ( 105 | self.instr.visalib.sessions[self.instr.session].interface.sock.getsockopt( 106 | socket.SOL_SOCKET, socket.SO_KEEPALIVE 107 | ) 108 | == 0 109 | ) 110 | 111 | 112 | @require_virtual_instr 113 | class TestTCPIPSocket(TCPIPSocketBaseTest): 114 | """Test pyvisa-py against a TCPIP SOCKET resource.""" 115 | 116 | #: Type of resource being tested in this test case. 117 | #: See RESOURCE_ADDRESSES in the __init__.py file of this package for 118 | #: acceptable values 119 | RESOURCE_TYPE = "TCPIP::SOCKET" 120 | 121 | #: Minimal timeout value accepted by the resource. When setting the timeout 122 | #: to VI_TMO_IMMEDIATE, Visa (Keysight at least) may actually use a 123 | #: different value depending on the values supported by the resource. 124 | MINIMAL_TIMEOUT = 1 125 | 126 | test_timeout = pytest.mark.xfail(copy_func(TCPIPSocketBaseTest.test_timeout)) 127 | 128 | test_attribute_handling = pytest.mark.xfail( 129 | copy_func(TCPIPSocketBaseTest.test_attribute_handling) 130 | ) 131 | 132 | test_stb = pytest.mark.xfail(copy_func(TCPIPSocketBaseTest.test_stb)) 133 | -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | 4 | Installation 5 | ============ 6 | 7 | Pyvisa-py is available on PyPI_ and can be easily installed using pip: 8 | 9 | pip install pyvisa-py 10 | 11 | 12 | Pyvisa-py runs on Python 3.6+. 13 | 14 | If you do not install any extra library pyvisa-py will only be able to access 15 | tcpip resources. The following sections will describe what extra libraries you 16 | need to install and how to configure them to use other resources. 17 | 18 | 19 | Ethernet resources: TCPIP INSTR/SOCKET 20 | -------------------------------------- 21 | 22 | Pyvisa-py relies on :py:mod:`socket` module in the Python Standard Library to 23 | interact with the instrument which you do not need to install any extra library 24 | to access those resources. 25 | 26 | To discover VXI-11 devices on all network interfaces, please install 27 | `psutil`_. Otherwise, discovery will only occur on the default network 28 | interface. 29 | 30 | Discovery of both HiSLIP and VICP devices relies on `mDNS`_, which is a protocol for 31 | service discovery in a local area network. To enable resource 32 | discovery for HiSLIP and VICP, you should install `zeroconf`_. 33 | 34 | The TCP/IP VICP protocol (proprietary to Teledyne LeCroy) depends on 35 | the `pyvicp`_ package. You should install this package if you need to 36 | use VICP. 37 | 38 | 39 | Serial resources: ASRL INSTR 40 | ---------------------------- 41 | 42 | To access serial resources, you should install PySerial_. Version 3.0 or newer 43 | is required. No special configuration is required. 44 | 45 | 46 | GPIB resources: GPIB INSTR 47 | -------------------------- 48 | 49 | On all platforms, using **GPIB** resources requires to install a gpib driver. 50 | On Windows, it is install as part of NI-VISA or Keysight VISA for example. On 51 | MacOSX, you should install the NI-488 library from National instrument. On 52 | Linux, you can use a commercial driver (NI) or the `linux-gpib`_ project. 53 | 54 | On Linux, `linux-gpib`_ comes with Python bindings so you do not have to 55 | install any extra library. 56 | On all systems with GPIB device drivers, GPIB support is available through 57 | `gpib-ctypes`_. 58 | 59 | You should not have to perform any special configuration after the install. 60 | 61 | 62 | USB resources: USB INSTR/RAW 63 | ---------------------------- 64 | 65 | For **USB** resources, you need to install PyUSB_ and a suitable backend. PyUSB_ 66 | relies on a USB driver library such as libusb 0.1, libusb 1.0, libusbx, 67 | libusb-win32 or OpenUSB as a suitable backend. Please refer to the PyUSB_ 68 | documentation for more details. 69 | 70 | On Windows, especially if you are using a user account without administrator 71 | privileges, you may install `pyocd/libusb-package`_ for a convenient way to 72 | provide the necessary libusb 1.0 DLL as a suitable PyUSB_ backend. 73 | 74 | On Unix systems, you may have to modify udev rules to allow non-root access to 75 | the device you are trying to connect to. The following tutorial describes how 76 | to do it https://www.xmodulo.com/change-usb-device-permission-linux.html. 77 | 78 | Note that USBTMC devices show up as both /dev/usbtmcN and /dev/bus/usb/NNN/NNN. 79 | Both instances need to have suitable permissions for pyvisa with pyvisa-py to 80 | be able to communicate. If only the /dev/usbtmcN has permissions then you get:: 81 | 82 | WARNING Found a device whose serial number cannot be read 83 | 84 | On Windows, you may have to uninstall the USBTMC-specific driver installed by 85 | Windows and re-install a generic driver. Please check `libusb's guide`_ for more 86 | details, but installing a ``WinUSB`` driver with Zadig_ should be a good start. 87 | 88 | Note that on Windows, devices that are already open cannot be detected and will 89 | not be returned by ``ResourceManager.list_resources``. 90 | 91 | 92 | How do I know if PyVISA-py is properly installed? 93 | ------------------------------------------------- 94 | 95 | Using the pyvisa information tool. Run in your console:: 96 | 97 | pyvisa-info 98 | 99 | You will get info about PyVISA, the installed backends and their options. 100 | 101 | 102 | Using the development version 103 | ----------------------------- 104 | 105 | You can install the latest development version (at your own risk) directly 106 | form GitHub_:: 107 | 108 | $ pip install -U git+https://github.com/pyvisa/pyvisa-py.git 109 | 110 | 111 | .. _PySerial: https://pythonhosted.org/pyserial/ 112 | .. _PyVISA: http://pyvisa.readthedocs.org/ 113 | .. _PyUSB: https://github.com/pyusb/pyusb 114 | .. _PyPI: https://pypi.python.org/pypi/PyVISA-py 115 | .. _GitHub: https://github.com/pyvisa/pyvisa-py 116 | .. _`National Instruments's VISA`: http://ni.com/visa/ 117 | .. _`LibreVISA`: http://www.librevisa.org/ 118 | .. _`issue tracker`: https://github.com/pyvisa/pyvisa-py/issues 119 | .. _`linux-gpib`: http://linux-gpib.sourceforge.net/ 120 | .. _`gpib-ctypes`: https://pypi.org/project/gpib-ctypes/ 121 | .. _`psutil`: https://pypi.org/project/psutil/ 122 | .. _`mDNS`: https://en.wikipedia.org/wiki/Multicast_DNS 123 | .. _`zeroconf`: https://pypi.org/project/zeroconf/ 124 | .. _`pyvicp`: https://pypi.org/project/pyvicp/ 125 | .. _`libusb's guide`: https://github.com/libusb/libusb/wiki/Windows#user-content-How_to_use_libusb_on_Windows 126 | .. _`Zadig`: https://zadig.akeo.ie/ 127 | .. _`pyocd/libusb-package`: https://pypi.org/project/libusb-package/ 128 | -------------------------------------------------------------------------------- /docs/source/faq.rst: -------------------------------------------------------------------------------- 1 | .. _faq: 2 | 3 | 4 | FAQ 5 | === 6 | 7 | 8 | Are all VISA attributes and methods implemented? 9 | ------------------------------------------------ 10 | 11 | No. We have implemented those attributes and methods that are most commonly 12 | needed. We would like to reach feature parity. If there is something that you 13 | need, let us know. 14 | 15 | 16 | Why are you developing this? 17 | ---------------------------- 18 | 19 | The IVI compliant VISA implementations available (`National Instruments NI-VISA`_ , 20 | `Keysight IO Libraries`_, `Tektronix TekVISA`_, etc) are proprietary libraries that only work on 21 | certain systems. We wanted to provide a compatible alternative. 22 | 23 | 24 | Are GBIP secondary addresses supported? 25 | --------------------------------------- 26 | 27 | GPIB secondary addresses are supported in NI-VISA fashion, meaning that the 28 | secondary address is not 96 to 126 as transmitted on the bus, but 0 to 30. 29 | 30 | For expample, `GPIB0::9::1::INSTR` is the address of the first VXI module 31 | controlled by a GPIB VXI command module set to primary address `9`, while 32 | the command module itself is found at `GPIB0::9::0::INSTR`, which is distinct 33 | from a pure primary address like `GPIB0::9::INSTR`. 34 | 35 | ``ResourceManager.list_resources()`` has become slower as a result, 36 | as it now needs to check 992 addresses per GPIB controller instead of just 31. 37 | 38 | For every primary address where no listener is detected, all 39 | secondary addresses are checked for listeners as well to find, for example, 40 | VXI modules controlled by an HP E1406A. 41 | 42 | For primary addresses where a listener is detected, no secondary addresses are 43 | checked as most devices simply ignore secondary addressing. 44 | 45 | If you have a device that reacts to the primary address and has different 46 | functionality on some secondary addresses, please leave a bug report. 47 | 48 | 49 | Can PyVISA-py be used from a VM? 50 | -------------------------------- 51 | 52 | Because PyVISA-py access hardware resources such as USB ports, running from a 53 | VM can cause issues like unexpected timeouts because the VM does not 54 | receive the response. You should consult your VM manual to determine 55 | if you are able to setup the VM in such a way that it works. See 56 | https://github.com/pyvisa/pyvisa-py/issues/243 for the kind of issue 57 | it can cause. 58 | 59 | 60 | Can PyVISA-py be used from a Docker container? 61 | ---------------------------------------------- 62 | As the Windows variant of Docker can forward neither USB ports nor GPIB 63 | interfaces, the obvious choice would be to connect via TCP/IP. The problem of a 64 | Docker container is that idle connections are disconnected by the VPN garbage 65 | collection. For this reason it is reasonable to enable keepalive packets. 66 | The VISA attribute `VI_ATTR_TCPIP_KEEPALIVE` has been modified to work 67 | for all TCP/IP instruments. Enabling this option can be done with: 68 | 69 | inst.set_visa_attribute(pyvisa.constants.ResourceAttribute.tcpip_keepalive, True) 70 | 71 | where `inst` is an active TCP/IP visa session. 72 | (see https://tech.xing.com/a-reason-for-unexplained-connection-timeouts-on-kubernetes-docker-abd041cf7e02 73 | if you want to read more about connection dropping in docker containers) 74 | 75 | 76 | Why not using LibreVISA? 77 | ------------------------ 78 | 79 | LibreVISA_ is still young and appears mostly unmaintained at this 80 | point (latest release is from 2013). 81 | However, you can already use it with the IVI backend as it has the same API. 82 | We think that PyVISA-py is easier to hack and we can quickly reach feature parity 83 | with other IVI-VISA implementation for message-based instruments. 84 | 85 | 86 | Why putting PyVISA in the middle? 87 | --------------------------------- 88 | 89 | Because it allows you to change the backend easily without changing your application. 90 | In other projects, we implemented classes to call USBTMC devices without PyVISA. 91 | But this leads to code duplication or an adapter class in your code. 92 | By using PyVISA as a frontend to many backends, we abstract these things 93 | from higher level applications. 94 | 95 | 96 | Why is my Ethernet instrument not working? 97 | ------------------------------------------ 98 | 99 | Some instruments, such as the Rigol DM3068 Digital Multimeter, 100 | expect a non-default parameter in order to communicate successfully over Ethernet. 101 | In the case of the DM3068, the VXI-11 lock timeout must be set to zero: 102 | 103 | >>> import pyvisa 104 | >>> rm = pyvisa.ResourceManager('@py') 105 | >>> dm3068 = rm.open_resource('TCPIP::rigol-dm3068-hostname::INSTR') 106 | >>> # default lock_timeout is still 10000ms at this point 107 | >>> rm.visalib.sessions[dm3068.session].lock_timeout = 0 108 | >>> # can now communicate successfully with the DM3068 109 | 110 | 111 | .. _PySerial: https://pythonhosted.org/pyserial/ 112 | .. _PyVISA: http://pyvisa.readthedocs.org/ 113 | .. _PyUSB: https://github.com/pyusb/pyusb 114 | .. _PyPI: https://pypi.python.org/pypi/PyVISA-py 115 | .. _GitHub: https://github.com/pyvisa/pyvisa-py 116 | .. _`National Instruments NI-VISA`: http://ni.com/visa/ 117 | .. _`LibreVISA`: http://www.librevisa.org/ 118 | .. _`issue tracker`: https://github.com/pyvisa/pyvisa-py/issues 119 | .. _`linux-gpib`: http://linux-gpib.sourceforge.net/ 120 | .. _`gpib-ctypes`: https://pypi.org/project/gpib-ctypes/ 121 | .. _`Tektronix TekVISA`: https://www.tek.com/en/support/software/driver/tekvisa-connectivity-software-v420 122 | .. _`Keysight IO Libraries`: https://www.keysight.com/us/en/lib/software-detail/computer-software/io-libraries-suite-downloads-2175637.html 123 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyvisa.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyvisa.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyvisa.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyvisa.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pyvisa" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyvisa" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /pyvisa_py/protocols/xdrlib.py: -------------------------------------------------------------------------------- 1 | # Taken from Python 3.12 and vendored since xdrlib will be removed in 3.13 2 | # Warning about deprecation was removed and code reformatted using black 3 | """Implements (a subset of) Sun XDR -- eXternal Data Representation. 4 | 5 | See: RFC 1014 6 | 7 | """ 8 | 9 | import struct 10 | from functools import wraps 11 | from io import BytesIO 12 | 13 | __all__ = ["ConversionError", "Error", "Packer", "Unpacker"] 14 | 15 | 16 | # exceptions 17 | class Error(Exception): 18 | """Exception class for this module. Use: 19 | 20 | except xdrlib.Error as var: 21 | # var has the Error instance for the exception 22 | 23 | Public ivars: 24 | msg -- contains the message 25 | 26 | """ 27 | 28 | def __init__(self, msg): 29 | self.msg = msg 30 | 31 | def __repr__(self): 32 | return repr(self.msg) 33 | 34 | def __str__(self): 35 | return str(self.msg) 36 | 37 | 38 | class ConversionError(Error): 39 | pass 40 | 41 | 42 | def raise_conversion_error(function): 43 | """Wrap any raised struct.errors in a ConversionError.""" 44 | 45 | @wraps(function) 46 | def result(self, value): 47 | try: 48 | return function(self, value) 49 | except struct.error as e: 50 | raise ConversionError(e.args[0]) from None 51 | 52 | return result 53 | 54 | 55 | class Packer: 56 | """Pack various data representations into a buffer.""" 57 | 58 | def __init__(self): 59 | self.reset() 60 | 61 | def reset(self): 62 | self.__buf = BytesIO() 63 | 64 | def get_buffer(self): 65 | return self.__buf.getvalue() 66 | 67 | # backwards compatibility 68 | get_buf = get_buffer 69 | 70 | @raise_conversion_error 71 | def pack_uint(self, x): 72 | self.__buf.write(struct.pack(">L", x)) 73 | 74 | @raise_conversion_error 75 | def pack_int(self, x): 76 | self.__buf.write(struct.pack(">l", x)) 77 | 78 | pack_enum = pack_int 79 | 80 | def pack_bool(self, x): 81 | if x: 82 | self.__buf.write(b"\0\0\0\1") 83 | else: 84 | self.__buf.write(b"\0\0\0\0") 85 | 86 | def pack_uhyper(self, x): 87 | try: 88 | self.pack_uint(x >> 32 & 0xFFFFFFFF) 89 | except (TypeError, struct.error) as e: 90 | raise ConversionError(e.args[0]) from None 91 | try: 92 | self.pack_uint(x & 0xFFFFFFFF) 93 | except (TypeError, struct.error) as e: 94 | raise ConversionError(e.args[0]) from None 95 | 96 | pack_hyper = pack_uhyper 97 | 98 | @raise_conversion_error 99 | def pack_float(self, x): 100 | self.__buf.write(struct.pack(">f", x)) 101 | 102 | @raise_conversion_error 103 | def pack_double(self, x): 104 | self.__buf.write(struct.pack(">d", x)) 105 | 106 | def pack_fstring(self, n, s): 107 | if n < 0: 108 | raise ValueError("fstring size must be nonnegative") 109 | data = s[:n] 110 | n = ((n + 3) // 4) * 4 111 | data = data + (n - len(data)) * b"\0" 112 | self.__buf.write(data) 113 | 114 | pack_fopaque = pack_fstring 115 | 116 | def pack_string(self, s): 117 | n = len(s) 118 | self.pack_uint(n) 119 | self.pack_fstring(n, s) 120 | 121 | pack_opaque = pack_string 122 | pack_bytes = pack_string 123 | 124 | def pack_list(self, list, pack_item): 125 | for item in list: 126 | self.pack_uint(1) 127 | pack_item(item) 128 | self.pack_uint(0) 129 | 130 | def pack_farray(self, n, list, pack_item): 131 | if len(list) != n: 132 | raise ValueError("wrong array size") 133 | for item in list: 134 | pack_item(item) 135 | 136 | def pack_array(self, list, pack_item): 137 | n = len(list) 138 | self.pack_uint(n) 139 | self.pack_farray(n, list, pack_item) 140 | 141 | 142 | class Unpacker: 143 | """Unpacks various data representations from the given buffer.""" 144 | 145 | def __init__(self, data): 146 | self.reset(data) 147 | 148 | def reset(self, data): 149 | self.__buf = data 150 | self.__pos = 0 151 | 152 | def get_position(self): 153 | return self.__pos 154 | 155 | def set_position(self, position): 156 | self.__pos = position 157 | 158 | def get_buffer(self): 159 | return self.__buf 160 | 161 | def done(self): 162 | if self.__pos < len(self.__buf): 163 | raise Error("unextracted data remains") 164 | 165 | def unpack_uint(self): 166 | i = self.__pos 167 | self.__pos = j = i + 4 168 | data = self.__buf[i:j] 169 | if len(data) < 4: 170 | raise EOFError 171 | return struct.unpack(">L", data)[0] 172 | 173 | def unpack_int(self): 174 | i = self.__pos 175 | self.__pos = j = i + 4 176 | data = self.__buf[i:j] 177 | if len(data) < 4: 178 | raise EOFError 179 | return struct.unpack(">l", data)[0] 180 | 181 | unpack_enum = unpack_int 182 | 183 | def unpack_bool(self): 184 | return bool(self.unpack_int()) 185 | 186 | def unpack_uhyper(self): 187 | hi = self.unpack_uint() 188 | lo = self.unpack_uint() 189 | return int(hi) << 32 | lo 190 | 191 | def unpack_hyper(self): 192 | x = self.unpack_uhyper() 193 | if x >= 0x8000000000000000: 194 | x = x - 0x10000000000000000 195 | return x 196 | 197 | def unpack_float(self): 198 | i = self.__pos 199 | self.__pos = j = i + 4 200 | data = self.__buf[i:j] 201 | if len(data) < 4: 202 | raise EOFError 203 | return struct.unpack(">f", data)[0] 204 | 205 | def unpack_double(self): 206 | i = self.__pos 207 | self.__pos = j = i + 8 208 | data = self.__buf[i:j] 209 | if len(data) < 8: 210 | raise EOFError 211 | return struct.unpack(">d", data)[0] 212 | 213 | def unpack_fstring(self, n): 214 | if n < 0: 215 | raise ValueError("fstring size must be nonnegative") 216 | i = self.__pos 217 | j = i + (n + 3) // 4 * 4 218 | if j > len(self.__buf): 219 | raise EOFError 220 | self.__pos = j 221 | return self.__buf[i : i + n] 222 | 223 | unpack_fopaque = unpack_fstring 224 | 225 | def unpack_string(self): 226 | n = self.unpack_uint() 227 | return self.unpack_fstring(n) 228 | 229 | unpack_opaque = unpack_string 230 | unpack_bytes = unpack_string 231 | 232 | def unpack_list(self, unpack_item): 233 | list = [] 234 | while (x := self.unpack_uint()) != 0: 235 | if x != 1: 236 | raise ConversionError("0 or 1 expected, got %r" % (x,)) 237 | item = unpack_item() 238 | list.append(item) 239 | return list 240 | 241 | def unpack_farray(self, n, unpack_item): 242 | list = [] 243 | for i in range(n): 244 | list.append(unpack_item()) 245 | return list 246 | 247 | def unpack_array(self, unpack_item): 248 | n = self.unpack_uint() 249 | return self.unpack_farray(n, unpack_item) 250 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | PyVISA-py Changelog 2 | =================== 3 | 4 | 0.9.0 (unreleased) 5 | ------------------ 6 | 7 | - Added read_stb, assert_trigger and gpib_control_run functions to usbtmc PR #532 8 | 9 | 0.8.1 (04-09-2025) 10 | ------------------ 11 | 12 | - allow users to easily change the VXI-11 lock timeout PR #496 13 | - ensure listing of GPIB resources works after PR #484 PR #523 14 | - add support for pyocd/libusb_package PyUSB backend PR #524 15 | 16 | 0.8.0 (01-04-2025) 17 | ------------------ 18 | 19 | - add support for USB and Ethernet Prologix adapters PR #484 20 | - improve message when USB device serial number is not readable PR #423 21 | - ignore network interface card with no subnet mask when discovering TCPIP 22 | resources PR #478 23 | - add VICP INSTR to the supported interface for TCPIP keep alive attr PR #477 24 | - add read_stb method for TCPIP HiSLIP client PR #429 25 | - fix usbtmc implementation to respect section 3.3 of the spec PR #449 26 | Read now reads from usb until a "short packet" or if all data (`transfer_size`) PR #465 27 | has been read (see specification), and only expects a header on the first packet received. 28 | - fix usbtmc implementation to properly discard the alignment bytes 29 | ensuring only the actual data (`transfer_size`) is retained in the message PR #465 30 | - Implemented partial USBTMC message functionality that allows reading the amount of bytes 31 | specified by host PR #470 32 | - add support for VI_ATTR_SUPPRESS_END_EN for USB resources PR #449 33 | - support open_timeout for TCPIP hislip resources PR #430 34 | - fix serial flow control configuration PR #483 35 | - Fix large data transfer using USBTMC PR #490 36 | 37 | 0.7.2 (07/03/2024) 38 | ------------------ 39 | 40 | - fix usbtmc to use MaxPacketSize reported by endpoint PR #417 41 | 42 | 0.7.1 (26/10/2023) 43 | ------------------ 44 | 45 | - add URL-support to ASLR devices PR #386 46 | - add support for GPIB secondary addresses 47 | - fix missing sock.close() in rpc _connect() 48 | - Adjusted how `iter_bytes` works to be more accurate to the VISA spec and removed 49 | it from the `serial` module (it can still be found in `common`) 50 | - fix HiSLIP message tracking after read timeout PR #376 51 | - handle read_termination of null in tcipip PR #394 52 | - fix tcpip keepalive PR #396 53 | - store more attributes for USB resources PR #399 54 | 55 | 0.7.0 (05/05/2023) 56 | ------------------ 57 | 58 | - add support for the flush operation with TCPIP::SOCKET resources PR #350 59 | - drop support for Python 3.7 PR #362 60 | - fix listing of available resources PR #362 61 | - fix hislip support for custom sub_addresses PR #359 62 | - fix bad USBRaw resource preventing enumeration of other resources PR #370 63 | 64 | 0.6.3 (17-02-2023) 65 | ------------------ 66 | 67 | - fix bad behavior on PyVISA 1.12 and hence on Python 3.7 PR #357 68 | 0.6.x is the last version that will support Python 3.7 69 | 70 | 0.6.2 (08-02-2023) 71 | ------------------ 72 | 73 | - fix usb resource handling by avoiding multiple calls to set_configuration PR #352 74 | - formatting fixes on files using "black" PR #352 75 | 76 | 0.6.1 (25-01-2023) 77 | ------------------ 78 | 79 | - fix listing resources when some optional dependencies are missing PR #349 80 | - properly list discovered TCPIP resources PR #349 81 | - fix pyvisa-info output for TCPIP and GPIB resources PR #349 82 | 83 | 0.6.0 (22-12-2022) 84 | ------------------ 85 | 86 | - fix writing large messages over TCPIP using the VXI-11 protocol PR #343 87 | - add support for the hislip protocol over TCPIP PR #331 88 | - allow to list TCPIP resources PR #326 89 | In order to discover resources over all subnets psutil needs to be installed 90 | - attempt to stabilize access to USBTMC resources PR #335 91 | Reduce the number of device reset performed and only set all settings if it 92 | is meaningful (more than one settings exist.) 93 | 94 | A huge thanks to @bobmacnamara for his work adding hislip and vicp support ! 95 | 96 | 0.5.3 (12-05-2022) 97 | ------------------ 98 | - fix tcp/ip connections dropping from inside Docker containers after 5 minute idling #285 99 | - fix ControlFlow.none as an invalid attribute in serial.py PR #317 100 | - VXI11 bug fix: skip over stale rx packets instead of raising an exception. PR #322 101 | - VXI11 bug fix: to ensure all data gets sent, replace calls to sock.send() 102 | with calls to sock.sendall(), and replace calls to sock.sendto() with 103 | calls to a routine that loops until all data is sent. PR #322 104 | 105 | 0.5.2 (04-02-2020) 106 | ------------------ 107 | 108 | - handle SUPPRESS_END_EN in usb.py to fix #293 PR #294 109 | - add python_requires to avoid people trying to get a 110 | new pyvisa-py on Python 2 PR #295 111 | This addresses pyvisa issue #578 112 | 113 | 0.5.1 (30-09-2020) 114 | ------------------ 115 | 116 | - list serial resources under Windows without the COM prefix #269 117 | - fix writing to serial resources PR #277 118 | - fix return value of USB close method PR #265 119 | - fix pyvisa version constraint PR #268 120 | 121 | 0.5.0 (16-09-2020) 122 | ------------------ 123 | 124 | In 0.5.0, the Python package installed in site-packages in now called pyvisa_py 125 | which makes it a valid python package. This change was decided because the old name 126 | was causing issues with tools such as Mypy and PyInstaller. 127 | 128 | - start running testsuite on Keysight buildbot PR #247 #252 129 | - fix import in gpib.py when using linux-gpib PR #246 130 | - fix opening some TCPIP resources PR #242 131 | Some instrument do not indicate that the returned packet is the last one, so 132 | fix the number of returned packet for a TCPIP INSTR resource. 133 | - add typing validation through mypy PR #238 134 | - use Numpy style dosctrings PR #238 135 | - format the code with black and isort PR #236 136 | - drop Python 2 support and run against PyVISA 1.11 PR #222 137 | - usbtmc: improve support for USB488 devices. PR #241 138 | For instrument that support REN_CONTROL, we now always assert the REN line. 139 | - fix a crash on Windows when opening multiple USBTMC devices 140 | 141 | 0.4.1 (2020-05-27) 142 | ------------------ 143 | 144 | - fix typo in tcpip.py PR #233 (back-ported) 145 | 146 | 0.4.0 (2020-05-06) 147 | ------------------ 148 | 149 | - support device lan name in TCPIP INSTR resources PR #226 150 | - fix handling of VXI11 flags in device_write PR #226 151 | - do not enforce 4 bytes padding RFC 1014 since some instrument do not respect 152 | it (Issue # 225) PR #226 153 | - fix not setting stop bits with serial devices PR #205 154 | - fix improper wait time before a timeout in the TCPIP backend PR # 173 155 | - add GPIB support for proprietary device drivers on Windows and Linux 156 | (experimental): try importing gpib-ctypes if linux-gpib is not present. 157 | fix #105 #137 158 | - fix return types of PyVisaLibrary and Session methods to match 159 | pyvisa.highlevel.VisaLibraryBase fix #169 PR #170 160 | - avoid double closing of gpib resources PR #171 161 | - fix initialization of timeout for the USB resources (the default was set 162 | before creating the underlying connection to which the timeout must be passed 163 | and was not). PR #167 164 | - implement USBTMC abort sequence after read timeout to avoid losing 165 | communication with the instrument after a timeout occurs. PR #179 166 | - fix custom timeout for USB instruments. PR #179 167 | - fix triggering for all protocols. PR #180 168 | - add support for "quirky" devices made by Rigol. PR #186 PR #207 169 | - add support for Visa flush operation. PR #208 170 | - fix reading large amounts of data from some instruments when using VXI-11. PR #209 171 | 172 | 0.3.1 (2018-09-12) 173 | ------------------ 174 | 175 | - Fix initialization of timeout (issue identified in TCPIP resources) PR #160 176 | 177 | 0.3 (2018-09-05) 178 | ---------------- 179 | 180 | - Fix handling of seesion registration under Python 3.7 PR #155 181 | - Add read_stb, assert_trigger, lock, unlock to highlevel PR #139 182 | - Fix timeout handling in usb PR #144 183 | - Add gpib_command and assert_trigger to GPIB PR # 136 184 | - Handle ValueError in usb list fix #131 PR #132 185 | - Fix reading on GPIB and implement clear and gpib_send_ifc PR #132 186 | - Do not error when listing USB devices PR #126 187 | - Fix an error in the handling of the termchar for TCPIP INSTR PR #126 188 | - Make list_resources return an empty tuple instead of erroring PR #121 189 | - Proper support for timeout in TCPIP INSTR sessions PR #120 #127 #130 #144 190 | - Proper encoding of data before transfer for all backends PR #119 191 | - Unify use of StatusCode PR #118 192 | - Improve handling of sessions attrs PR #116 193 | - TCPIP SOCKET timeout handling improvement PR #115 194 | - Fix compatibility with pyserial 3.0 PR #112 195 | - TCPIP SOCKET handler read should not block PR #107 196 | - TCPIP error handling fixes PR #100 197 | - Use repr() instead of str() to log RPC record PR #97 198 | - Speed up large transfer over GPIB 2beb52a5bcea2dae32d4a9908dc19f7874bfc0b7 199 | - Catch GPIB errors while enumerating devices 9fea9d5c40cc6c33ce1244c209e5e576a33abfc2 200 | - Add a serial poll function to GPIB backend PR #67 201 | - Handle timeout in USB TMC backend PR #64 202 | - Make USB TMC backend faster by transferring multiple bytes PR #63 203 | - Fix issue with encoding before data transfer PR #59 # 204 | - Get Linux GPIB version PR #55 205 | - Fix broken import in TCPIP sessions PR #51 206 | 207 | 208 | 0.2 (2015-08-25) 209 | ---------------- 210 | 211 | - Added support for TCPIP Socket. 212 | (Issue #38, thanks Thorsten Liebig) 213 | - Added support for GPIB INSTR using linux-gpib. 214 | (Issue #24, thanks bessman) 215 | - Added support for USB RAW. 216 | (Issue #18, kopp) 217 | - Better error reporting when pyusb or pyserial is missing. 218 | - Fixed logging of unicode strings. 219 | (Issue #54) 220 | - Fixed timeout in SerialSession. 221 | (Issue #44) 222 | - Moved resource name parsing to PyVISA. 223 | - VXI11 protocol performance enhancement. 224 | (thanks alexforencich) 225 | - Improved pyusb importing. 226 | - Fixed large binary reads in TCPIP. 227 | - Added backend information to logger. 228 | - Use pyvisa compat/struct.py for python < 2.7.8 229 | (thanks Martin Ritter) 230 | 231 | 232 | 233 | 0.1 (2015-02-08) 234 | ---------------- 235 | 236 | - Initial release. Preliminary support for: 237 | - USB INSTR 238 | - TCPIP INSTR 239 | - ASRL INSTR 240 | -------------------------------------------------------------------------------- /pyvisa_py/protocols/usbutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Serial Session implementation using PyUSB. 3 | 4 | See the following link for more information about USB. 5 | 6 | http://www.beyondlogic.org/usbnutshell/usb5.shtml 7 | 8 | This file is an offspring of the Lantz Project. 9 | 10 | :copyright: 2014-2024 by PyVISA-py Authors, see AUTHORS for more details. 11 | :license: MIT, see LICENSE for more details. 12 | 13 | """ 14 | 15 | from fnmatch import fnmatch 16 | 17 | import usb 18 | from usb.util import find_descriptor as usb_find_desc 19 | 20 | ClassCodes = { 21 | 0x00: ("Device", "Use class information in the Interface Descriptors"), 22 | 0x01: ("Interface", "Audio"), 23 | 0x02: ("Both", "Communications and CDC Control"), 24 | 0x03: ("Interface", "HID (Human Interface Device)"), 25 | 0x05: ("Interface", "Physical"), 26 | 0x06: ("Interface", "Image"), 27 | 0x07: ("Interface", "Printer"), 28 | 0x08: ("Interface", "Mass Storage"), 29 | 0x09: ("Device", "Hub"), 30 | 0x0A: ("Interface", "CDC-Data"), 31 | 0x0B: ("Interface", "Smart Card"), 32 | 0x0D: ("Interface", "Content Security"), 33 | 0x0E: ("Interface", "Video"), 34 | 0x0F: ("Interface", "Personal Healthcare"), 35 | 0x10: ("Interface", "Audio/Video Devices"), 36 | 0xDC: ("Both", "Diagnostic Device"), 37 | 0xE0: ("Interface", "Wireless Controller"), 38 | 0xEF: ("Both", "Miscellaneous"), 39 | 0xFE: ("Interface", "Application Specific"), 40 | 0xFF: ("Both", "Vendor Specific"), 41 | } 42 | 43 | # None is 0xxx 44 | AllCodes = { 45 | (0x00, 0x00, 0x00): "Use class code info from Interface Descriptors", 46 | (0x01, None, None): "Audio device", 47 | (0x02, None, None): "Communication device class", 48 | (0x03, None, None): "HID device class", 49 | (0x05, None, None): "Physical device class", 50 | (0x06, 0x01, 0x01): "Still Imaging device", 51 | (0x07, None, None): "Printer device", 52 | (0x08, None, None): "Mass Storage device", 53 | (0x09, 0x00, 0x00): "Full speed Hub", 54 | (0x09, 0x00, 0x01): "Hi-speed hub with single TT", 55 | (0x09, 0x00, 0x02): "Hi-speed hub with multiple TTs", 56 | (0x0A, None, None): "CDC data device", 57 | (0x0B, None, None): "Smart Card device", 58 | (0x0D, 0x00, 0x00): "Content Security device", 59 | (0x0E, None, None): "Video device", 60 | (0x0F, None, None): "Personal Healthcare device", 61 | (0x10, 0x01, 0x00): "Control Interface", 62 | (0x10, 0x02, 0x00): "Data Video Streaming Interface", 63 | (0x10, 0x03, 0x00): "VData Audio Streaming Interface", 64 | (0xDC, 0x01, 0x01): "USB2 Compliance Device", 65 | (0xE0, 0x01, 0x01): "Bluetooth Programming Interface.", 66 | (0xE0, 0x01, 0x02): "UWB Radio Control Interface.", 67 | (0xE0, 0x01, 0x03): "Remote NDIS", 68 | (0xE0, 0x01, 0x04): "Bluetooth AMP Controller.", 69 | (0xE0, 0x2, 0x01): "Host Wire Adapter Control/Data interface.", 70 | (0xE0, 0x2, 0x02): "Device Wire Adapter Control/Data interface.", 71 | (0xE0, 0x2, 0x03): "Device Wire Adapter Isochronous interface.", 72 | (0xEF, 0x01, 0x01): "Active Sync device.", 73 | (0xEF, 0x01, 0x02): "Palm Sync. This class code can be used in either " 74 | "Device or Interface Descriptors.", 75 | (0xEF, 0x02, 0x01): "Interface Association Descriptor.", 76 | (0xEF, 0x02, 0x02): "Wire Adapter Multifunction Peripheral programming interface.", 77 | (0xEF, 0x03, 0x01): "Cable Based Association Framework.", 78 | (0xEF, 0x04, 0x01): "RNDIS over Ethernet. Connecting a host to the Internet via " 79 | "Ethernet mobile device. The device appears to the host as an" 80 | "Ethernet gateway device. This class code may only be used in " 81 | "Interface Descriptors.", 82 | (0xEF, 0x04, 0x02): "RNDIS over WiFi. Connecting a host to the Internet via WiFi " 83 | "enabled mobile device. The device represents itself to the host" 84 | "as an 802.11 compliant network device. This class code may only" 85 | "be used in Interface Descriptors.", 86 | (0xEF, 0x04, 0x03): "RNDIS over WiMAX. Connecting a host to the Internet via WiMAX " 87 | "enabled mobile device. The device is represented to the host " 88 | "as an 802.16 network device. This class code may only be used " 89 | "in Interface Descriptors.", 90 | ( 91 | 0xEF, 92 | 0x04, 93 | 0x04, 94 | ): "RNDIS over WWAN. Connecting a host to the Internet via a device " 95 | "using mobile broadband, i.e. WWAN (GSM/CDMA). This class code may " 96 | "only be used in Interface Descriptors.", 97 | ( 98 | 0xEF, 99 | 0x04, 100 | 0x05, 101 | ): "RNDIS for Raw IPv4. Connecting a host to the Internet using raw " 102 | "IPv4 via non-Ethernet mobile device. Devices that provide raw " 103 | "IPv4, not in an Ethernet packet, may use this form to in lieu of " 104 | "other stock types. " 105 | "This class code may only be used in Interface Descriptors.", 106 | ( 107 | 0xEF, 108 | 0x04, 109 | 0x06, 110 | ): "RNDIS for Raw IPv6. Connecting a host to the Internet using raw " 111 | "IPv6 via non-Ethernet mobile device. Devices that provide raw " 112 | "IPv6, not in an Ethernet packet, may use this form to in lieu of " 113 | "other stock types. " 114 | "This class code may only be used in Interface Descriptors.", 115 | ( 116 | 0xEF, 117 | 0x04, 118 | 0x07, 119 | ): "RNDIS for GPRS. Connecting a host to the Internet over GPRS mobile " 120 | "device using the device,Äôs cellular radio.", 121 | (0xEF, 0x05, 0x00): "USB3 Vision Control Interface", 122 | (0xEF, 0x05, 0x01): "USB3 Vision Event Interface", 123 | (0xEF, 0x05, 0x02): "USB3 Vision Streaming Interface", 124 | (0xFE, 0x01, 0x01): "Device Firmware Upgrade.", 125 | (0xFE, 0x02, 0x00): "IRDA Bridge device.", 126 | (0xFE, 0x03, 0x00): "USB Test and Measurement Device.", 127 | ( 128 | 0xFE, 129 | 0x03, 130 | 0x01, 131 | ): "USB Test and Measurement Device conforming to the USBTMC USB488 Subclass", 132 | (0xFF, None, None): "Vendor specific", 133 | } 134 | 135 | 136 | def ep_attributes(ep): 137 | c = ep.bmAttributes 138 | attrs = [] 139 | tp = c & usb.ENDPOINT_TYPE_MASK 140 | if tp == usb.ENDPOINT_TYPE_CONTROL: 141 | attrs.append("Control") 142 | elif tp == usb.ENDPOINT_TYPE_ISOCHRONOUS: 143 | attrs.append("Isochronous") 144 | elif tp == usb.ENDPOINT_TYPE_BULK: 145 | attrs.append("Bulk") 146 | elif tp == usb.ENDPOINT_TYPE_INTERRUPT: 147 | attrs.append("Interrupt") 148 | 149 | sync = (c & 12) >> 2 150 | if sync == 0: 151 | attrs.append("No sync") 152 | elif sync == 1: 153 | attrs.append("Async") 154 | elif sync == 2: 155 | attrs.append("Adaptive") 156 | elif sync == 3: 157 | attrs.append("Sync") 158 | usage = (c & 48) >> 4 159 | if usage == 0: 160 | attrs.append("Data endpoint") 161 | elif usage == 1: 162 | attrs.append("Feedback endpoint") 163 | elif usage == 2: 164 | attrs.append("Subordinate Feedback endpoint") 165 | elif usage == 3: 166 | attrs.append("Reserved") 167 | 168 | return ", ".join(attrs) 169 | 170 | 171 | def find_devices( 172 | vendor=None, product=None, serial_number=None, custom_match=None, **kwargs 173 | ): 174 | """Find connected USB devices matching certain keywords. 175 | 176 | Wildcards can be used for vendor, product and serial_number. 177 | 178 | :param vendor: name or id of the vendor (manufacturer) 179 | :param product: name or id of the product 180 | :param serial_number: serial number. 181 | :param custom_match: callable returning True or False that takes a device as only input. 182 | :param kwargs: other properties to match. See usb.core.find 183 | :return: 184 | """ 185 | kwargs = kwargs or {} 186 | attrs = {} 187 | if isinstance(vendor, str): 188 | attrs["manufacturer"] = vendor 189 | elif vendor is not None: 190 | kwargs["idVendor"] = vendor 191 | 192 | if isinstance(product, str): 193 | attrs["product"] = product 194 | elif product is not None: 195 | kwargs["idProduct"] = product 196 | 197 | if serial_number: 198 | attrs["serial_number"] = str(serial_number) 199 | 200 | if attrs: 201 | 202 | def cm(dev): 203 | if custom_match is not None and not custom_match(dev): 204 | return False 205 | for attr, pattern in attrs.items(): 206 | try: 207 | value = getattr(dev, attr) 208 | except (NotImplementedError, ValueError): 209 | return False 210 | if not fnmatch(value.lower(), pattern.lower()): 211 | return False 212 | return True 213 | 214 | else: 215 | cm = custom_match 216 | 217 | try: 218 | devices = usb.core.find(find_all=True, custom_match=cm, **kwargs) 219 | except usb.core.NoBackendError as e1: 220 | try: 221 | import libusb_package 222 | 223 | devices = libusb_package.find(find_all=True, custom_match=cm, **kwargs) 224 | except ImportError as e2: 225 | raise e1 from e2 226 | 227 | return devices 228 | 229 | 230 | def find_interfaces(device, **kwargs): 231 | """ 232 | :param device: 233 | :return: 234 | """ 235 | interfaces = [] 236 | try: 237 | for cfg in device: 238 | try: 239 | interfaces.extend(usb_find_desc(cfg, find_all=True, **kwargs)) 240 | except Exception: 241 | pass 242 | except Exception: 243 | pass 244 | return interfaces 245 | 246 | 247 | def find_endpoint(interface, direction, type): 248 | ep = usb_find_desc( 249 | interface, 250 | custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) 251 | == direction 252 | and usb.util.endpoint_type(e.bmAttributes) == type, 253 | ) 254 | return ep 255 | 256 | 257 | def _patch_endpoint(ep, log_func=print): 258 | _read = ep.read 259 | _write = ep.write 260 | 261 | def new_read(*args, **kwargs): 262 | log_func("---") 263 | log_func("reading from {}".format(ep.bEndpointAddress)) 264 | log_func("args: {}".format(args)) 265 | log_func("kwargs: {}".format(kwargs)) 266 | ret = _read(*args, **kwargs) 267 | log_func("returned", ret) 268 | log_func("---") 269 | return ret 270 | 271 | def new_write(*args, **kwargs): 272 | log_func("---") 273 | log_func("writing to {}".format(ep.bEndpointAddress)) 274 | log_func("args: {}".format(args)) 275 | log_func("kwargs: {}".format(kwargs)) 276 | ret = _write(*args, **kwargs) 277 | log_func("returned", ret) 278 | log_func("---") 279 | return ret 280 | 281 | ep.read = new_read 282 | ep.write = new_write 283 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # PyVISA documentation build configuration file 4 | # 5 | # This file is execfile()d with the current directory set to its containing dir. 6 | # 7 | # Note that not all possible configuration values are present in this 8 | # autogenerated file. 9 | # 10 | # All configuration values have a default; values that are commented out 11 | # serve to show the default. 12 | 13 | import datetime 14 | from importlib.metadata import version as get_version 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | # needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [ 29 | "sphinx.ext.autodoc", 30 | "sphinx.ext.doctest", 31 | "sphinx.ext.intersphinx", 32 | "sphinx.ext.coverage", 33 | "sphinx.ext.viewcode", 34 | "sphinx.ext.mathjax", 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ["_templates"] 39 | 40 | # The suffix of source filenames. 41 | source_suffix = ".rst" 42 | 43 | # The encoding of source files. 44 | # source_encoding = 'utf-8-sig' 45 | 46 | # The master toctree document. 47 | master_doc = "index" 48 | 49 | # General information about the project. 50 | project = "PyVISA-Py" 51 | author = "PyVISA Authors" 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | version = get_version("pyvisa_py") 57 | release = version 58 | this_year = datetime.date.today().year 59 | copyright = "%s, %s" % (this_year, author) 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | # language = None 64 | 65 | # There are two options for replacing |today|: either, you set today to some 66 | # non-false value, then it is used: 67 | # today = '' 68 | # Else, today_fmt is used as the format for a strftime call. 69 | # today_fmt = '%B %d, %Y' 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | exclude_patterns = ["_build"] 74 | 75 | # The reST default role (used for this markup: `text`) to use for all documents. 76 | # default_role = None 77 | 78 | # If true, '()' will be appended to :func: etc. cross-reference text. 79 | # add_function_parentheses = True 80 | 81 | # If true, the current module name will be prepended to all description 82 | # unit titles (such as .. function::). 83 | # add_module_names = True 84 | 85 | # If true, sectionauthor and moduleauthor directives will be shown in the 86 | # output. They are ignored by default. 87 | # show_authors = False 88 | 89 | # The name of the Pygments (syntax highlighting) style to use. 90 | pygments_style = "sphinx" 91 | 92 | # A list of ignored prefixes for module index sorting. 93 | # modindex_common_prefix = [] 94 | 95 | 96 | # -- Options for HTML output --------------------------------------------------- 97 | # The theme to use for HTML and HTML Help pages. See the documentation for 98 | # a list of builtin themes. 99 | html_theme = "sphinx_rtd_theme" 100 | 101 | # Theme options are theme-specific and customize the look and feel of a theme 102 | # further. For a list of options available for each theme, see the 103 | # documentation. 104 | # html_theme_options = {} 105 | 106 | # Add any paths that contain custom themes here, relative to this directory. 107 | # html_theme_path = [] 108 | # html_theme_path = ['_themes'] 109 | 110 | # The name for this set of Sphinx documents. If None, it defaults to 111 | # " v documentation". 112 | # html_title = None 113 | 114 | # A shorter title for the navigation bar. Default is the same as html_title. 115 | # html_short_title = None 116 | 117 | # The name of an image file (relative to this directory) to place at the top 118 | # of the sidebar. 119 | # html_logo = None 120 | 121 | # The name of an image file (within the static path) to use as favicon of the 122 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 123 | # pixels large. 124 | # html_favicon = None 125 | 126 | # Add any paths that contain custom static files (such as style sheets) here, 127 | # relative to this directory. They are copied after the builtin static files, 128 | # so a file named "default.css" will overwrite the builtin "default.css". 129 | html_static_path = ["_static"] 130 | 131 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 132 | # using the given strftime format. 133 | # html_last_updated_fmt = '%b %d, %Y' 134 | 135 | # If true, SmartyPants will be used to convert quotes and dashes to 136 | # typographically correct entities. 137 | # html_use_smartypants = True 138 | 139 | # Custom sidebar templates, maps document names to template names. 140 | # html_sidebars = {} 141 | html_sidebars = { 142 | "index": ["sidebarintro.html", "sourcelink.html", "searchbox.html"], 143 | "**": [ 144 | "sidebarlogo.html", 145 | "localtoc.html", 146 | "relations.html", 147 | "sourcelink.html", 148 | "searchbox.html", 149 | ], 150 | } 151 | 152 | # Additional templates that should be rendered to pages, maps page names to 153 | # template names. 154 | # html_additional_pages = {} 155 | 156 | # If false, no module index is generated. 157 | # html_domain_indices = True 158 | 159 | # If false, no index is generated. 160 | # html_use_index = True 161 | 162 | # If true, the index is split into individual pages for each letter. 163 | # html_split_index = False 164 | 165 | # If true, links to the reST sources are added to the pages. 166 | # html_show_sourcelink = True 167 | 168 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 169 | # html_show_sphinx = True 170 | 171 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 172 | # html_show_copyright = True 173 | 174 | # If true, an OpenSearch description file will be output, and all pages will 175 | # contain a tag referring to it. The value of this option must be the 176 | # base URL from which the finished HTML is served. 177 | # html_use_opensearch = '' 178 | 179 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 180 | # html_file_suffix = None 181 | 182 | # Output file base name for HTML help builder. 183 | htmlhelp_basename = "pyvisa-pytdoc" 184 | 185 | 186 | # -- Options for LaTeX output -------------------------------------------------- 187 | 188 | # latex_elements = { 189 | # # The paper size ('letterpaper' or 'a4paper'). 190 | # #'papersize': 'letterpaper', 191 | # # The font size ('10pt', '11pt' or '12pt'). 192 | # #'pointsize': '10pt', 193 | # # Additional stuff for the LaTeX preamble. 194 | # #'preamble': '', 195 | # } 196 | 197 | # Grouping the document tree into LaTeX files. List of tuples 198 | # (source start file, target name, title, author, documentclass [howto/manual]). 199 | latex_documents = [ 200 | ("index", "pyvisa-py.tex", "PyVISA Documentation", "PyVISA Authors", "manual"), 201 | ] 202 | 203 | # The name of an image file (relative to this directory) to place at the top of 204 | # the title page. 205 | # latex_logo = None 206 | 207 | # For "manual" documents, if this is true, then toplevel headings are parts, 208 | # not chapters. 209 | # latex_use_parts = False 210 | 211 | # If true, show page references after internal links. 212 | # latex_show_pagerefs = False 213 | 214 | # If true, show URL addresses after external links. 215 | # latex_show_urls = False 216 | 217 | # Documents to append as an appendix to all manuals. 218 | # latex_appendices = [] 219 | 220 | # If false, no module index is generated. 221 | # latex_domain_indices = True 222 | 223 | 224 | # -- Options for manual page output -------------------------------------------- 225 | 226 | # One entry per manual page. List of tuples 227 | # (source start file, name, description, authors, manual section). 228 | man_pages = [("index", "pyvisa-py", "PyVISA Documentation", ["PyVISA Authors"], 1)] 229 | 230 | # If true, show URL addresses after external links. 231 | # man_show_urls = False 232 | 233 | 234 | # -- Options for Texinfo output ------------------------------------------------ 235 | 236 | # Grouping the document tree into Texinfo files. List of tuples 237 | # (source start file, target name, title, author, 238 | # dir menu entry, description, category) 239 | texinfo_documents = [ 240 | ( 241 | "index", 242 | "PyVISA", 243 | "PyVISA Documentation", 244 | "PyVISA Authors", 245 | "PyVISA", 246 | "One line description of project.", 247 | "Miscellaneous", 248 | ), 249 | ] 250 | 251 | # Documents to append as an appendix to all manuals. 252 | # texinfo_appendices = [] 253 | 254 | # If false, no module index is generated. 255 | # texinfo_domain_indices = True 256 | 257 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 258 | # texinfo_show_urls = 'footnote' 259 | 260 | 261 | # -- Options for Epub output --------------------------------------------------- 262 | 263 | # Bibliographic Dublin Core info. 264 | epub_title = project 265 | epub_author = author 266 | epub_publisher = author 267 | epub_copyright = copyright 268 | 269 | # The language of the text. It defaults to the language option 270 | # or en if the language is not set. 271 | # epub_language = '' 272 | 273 | # The scheme of the identifier. Typical schemes are ISBN or URL. 274 | # epub_scheme = '' 275 | 276 | # The unique identifier of the text. This can be a ISBN number 277 | # or the project homepage. 278 | # epub_identifier = '' 279 | 280 | # A unique identification for the text. 281 | # epub_uid = '' 282 | 283 | # A tuple containing the cover image and cover page html template filenames. 284 | # epub_cover = () 285 | 286 | # HTML files that should be inserted before the pages created by sphinx. 287 | # The format is a list of tuples containing the path and title. 288 | # epub_pre_files = [] 289 | 290 | # HTML files shat should be inserted after the pages created by sphinx. 291 | # The format is a list of tuples containing the path and title. 292 | # epub_post_files = [] 293 | 294 | # A list of files that should not be packed into the epub file. 295 | # epub_exclude_files = [] 296 | 297 | # The depth of the table of contents in toc.ncx. 298 | # epub_tocdepth = 3 299 | 300 | # Allow duplicate toc entries. 301 | # epub_tocdup = True 302 | 303 | 304 | # Example configuration for intersphinx: refer to the Python standard library. 305 | intersphinx_mapping = {"python": ("http://docs.python.org/3", None)} 306 | -------------------------------------------------------------------------------- /pyvisa_py/protocols/vxi11.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Implements a VX11 Session using Python Standard Library. 3 | 4 | Based on Python Sun RPC Demo and Alex Forencich python-vx11 5 | 6 | This file is an offspring of the Lantz project. 7 | 8 | :copyright: 2014-2024 by PyVISA-py Authors, see AUTHORS for more details. 9 | :license: MIT, see LICENSE for more details. 10 | 11 | """ 12 | 13 | import enum 14 | import socket 15 | 16 | from . import rpc 17 | 18 | # fmt: off 19 | # VXI-11 RPC constants 20 | 21 | # Device async 22 | DEVICE_ASYNC_PROG = 0x0607B0 23 | DEVICE_ASYNC_VERS = 1 24 | DEVICE_ABORT = 1 25 | 26 | # Device core 27 | DEVICE_CORE_PROG = 0x0607AF 28 | DEVICE_CORE_VERS = 1 29 | CREATE_LINK = 10 30 | DEVICE_WRITE = 11 31 | DEVICE_READ = 12 32 | DEVICE_READSTB = 13 33 | DEVICE_TRIGGER = 14 34 | DEVICE_CLEAR = 15 35 | DEVICE_REMOTE = 16 36 | DEVICE_LOCAL = 17 37 | DEVICE_LOCK = 18 38 | DEVICE_UNLOCK = 19 39 | DEVICE_ENABLE_SRQ = 20 40 | DEVICE_DOCMD = 22 41 | DESTROY_LINK = 23 42 | CREATE_INTR_CHAN = 25 43 | DESTROY_INTR_CHAN = 26 44 | 45 | # Device intr 46 | DEVICE_INTR_PROG = 0x0607B1 47 | DEVICE_INTR_VERS = 1 48 | DEVICE_INTR_SRQ = 30 49 | 50 | 51 | # Error states 52 | class ErrorCodes(enum.IntEnum): 53 | no_error = 0 54 | syntax_error = 1 55 | device_not_accessible = 3 56 | invalid_link_identifier = 4 57 | parameter_error = 5 58 | channel_not_established = 6 59 | operation_not_supported = 8 60 | out_of_resources = 9 61 | device_locked_by_another_link = 11 62 | no_lock_held_by_this_link = 12 63 | io_timeout = 15 64 | io_error = 17 65 | abort = 23 66 | channel_already_established = 29 67 | 68 | 69 | # Flags 70 | OP_FLAG_WAIT_BLOCK = 1 71 | OP_FLAG_END = 8 72 | OP_FLAG_TERMCHAR_SET = 128 73 | 74 | RX_REQCNT = 1 75 | RX_CHR = 2 76 | RX_END = 4 77 | 78 | # fmt: on 79 | 80 | 81 | class Vxi11Error(Exception): 82 | pass 83 | 84 | 85 | class Vxi11Packer(rpc.Packer): 86 | def pack_device_link(self, link): 87 | self.pack_int(link) 88 | 89 | def pack_create_link_parms(self, params): 90 | id, lock_device, lock_timeout, device = params 91 | self.pack_int(id) 92 | self.pack_bool(lock_device) 93 | self.pack_uint(lock_timeout) 94 | self.pack_string(device.encode("ascii")) 95 | 96 | def pack_device_write_parms(self, params): 97 | link, io_timeout, lock_timeout, flags, data = params 98 | self.pack_int(link) 99 | self.pack_uint(io_timeout) 100 | self.pack_uint(lock_timeout) 101 | self.pack_int(flags) 102 | self.pack_opaque(data) 103 | 104 | def pack_device_read_parms(self, params): 105 | link, request_size, io_timeout, lock_timeout, flags, term_char = params 106 | self.pack_int(link) 107 | self.pack_uint(request_size) 108 | self.pack_uint(io_timeout) 109 | self.pack_uint(lock_timeout) 110 | self.pack_int(flags) 111 | self.pack_int(term_char) 112 | 113 | def pack_device_generic_parms(self, params): 114 | link, flags, lock_timeout, io_timeout = params 115 | self.pack_int(link) 116 | self.pack_int(flags) 117 | self.pack_uint(lock_timeout) 118 | self.pack_uint(io_timeout) 119 | 120 | def pack_device_remote_func_parms(self, params): 121 | host_addr, host_port, prog_num, prog_vers, prog_family = params 122 | self.pack_uint(host_addr) 123 | self.pack_uint(host_port) 124 | self.pack_uint(prog_num) 125 | self.pack_uint(prog_vers) 126 | self.pack_int(prog_family) 127 | 128 | def pack_device_enable_srq_parms(self, params): 129 | link, enable, handle = params 130 | self.pack_int(link) 131 | self.pack_bool(enable) 132 | if len(handle) > 40: 133 | raise Vxi11Error("array length too long") 134 | self.pack_opaque(handle) 135 | 136 | def pack_device_lock_parms(self, params): 137 | link, flags, lock_timeout = params 138 | self.pack_int(link) 139 | self.pack_int(flags) 140 | self.pack_uint(lock_timeout) 141 | 142 | def pack_device_docmd_parms(self, params): 143 | ( 144 | link, 145 | flags, 146 | io_timeout, 147 | lock_timeout, 148 | cmd, 149 | network_order, 150 | datasize, 151 | data_in, 152 | ) = params 153 | self.pack_int(link) 154 | self.pack_int(flags) 155 | self.pack_uint(io_timeout) 156 | self.pack_uint(lock_timeout) 157 | self.pack_int(cmd) 158 | self.pack_bool(network_order) 159 | self.pack_int(datasize) 160 | self.pack_opaque(data_in) 161 | 162 | 163 | class Vxi11Unpacker(rpc.Unpacker): 164 | def unpack_device_link(self): 165 | return self.unpack_int() 166 | 167 | def unpack_device_error(self): 168 | return self.unpack_int() 169 | 170 | def unpack_create_link_resp(self): 171 | error = self.unpack_int() 172 | link = self.unpack_int() 173 | abort_port = self.unpack_uint() 174 | max_recv_size = self.unpack_uint() 175 | return error, link, abort_port, max_recv_size 176 | 177 | def unpack_device_write_resp(self): 178 | error = self.unpack_int() 179 | size = self.unpack_uint() 180 | return error, size 181 | 182 | def unpack_device_read_resp(self): 183 | error = self.unpack_int() 184 | reason = self.unpack_int() 185 | data = self.unpack_opaque() 186 | return error, reason, data 187 | 188 | def unpack_device_read_stb_resp(self): 189 | error = self.unpack_int() 190 | stb = self.unpack_uint() 191 | return error, stb 192 | 193 | def unpack_device_docmd_resp(self): 194 | error = self.unpack_int() 195 | data_out = self.unpack_opaque() 196 | return error, data_out 197 | 198 | 199 | class CoreClient(rpc.TCPClient): 200 | def __init__(self, host, open_timeout=5000): 201 | self.packer = Vxi11Packer() 202 | self.unpacker = Vxi11Unpacker("") 203 | super(CoreClient, self).__init__( 204 | host, DEVICE_CORE_PROG, DEVICE_CORE_VERS, open_timeout 205 | ) 206 | 207 | def create_link(self, id, lock_device, lock_timeout, name): 208 | params = (id, lock_device, lock_timeout, name) 209 | try: 210 | return self.make_call( 211 | CREATE_LINK, 212 | params, 213 | self.packer.pack_create_link_parms, 214 | self.unpacker.unpack_create_link_resp, 215 | ) 216 | except socket.timeout: 217 | return ErrorCodes.device_not_accessible, None, None, None 218 | 219 | def device_write(self, link, io_timeout, lock_timeout, flags, data): 220 | params = (link, io_timeout, lock_timeout, flags, data) 221 | try: 222 | return self.make_call( 223 | DEVICE_WRITE, 224 | params, 225 | self.packer.pack_device_write_parms, 226 | self.unpacker.unpack_device_write_resp, 227 | ) 228 | except socket.timeout as e: 229 | return ErrorCodes.io_error, e.args[0] 230 | 231 | def device_read( 232 | self, link, request_size, io_timeout, lock_timeout, flags, term_char 233 | ): 234 | params = (link, request_size, io_timeout, lock_timeout, flags, term_char) 235 | try: 236 | return self.make_call( 237 | DEVICE_READ, 238 | params, 239 | self.packer.pack_device_read_parms, 240 | self.unpacker.unpack_device_read_resp, 241 | ) 242 | except socket.timeout as e: 243 | return ErrorCodes.io_error, e.args[0], "" 244 | 245 | def device_read_stb(self, link, flags, lock_timeout, io_timeout): 246 | params = (link, flags, lock_timeout, io_timeout) 247 | return self.make_call( 248 | DEVICE_READSTB, 249 | params, 250 | self.packer.pack_device_generic_parms, 251 | self.unpacker.unpack_device_read_stb_resp, 252 | ) 253 | 254 | def device_trigger(self, link, flags, lock_timeout, io_timeout): 255 | params = (link, flags, lock_timeout, io_timeout) 256 | return self.make_call( 257 | DEVICE_TRIGGER, 258 | params, 259 | self.packer.pack_device_generic_parms, 260 | self.unpacker.unpack_device_error, 261 | ) 262 | 263 | def device_clear(self, link, flags, lock_timeout, io_timeout): 264 | params = (link, flags, lock_timeout, io_timeout) 265 | return self.make_call( 266 | DEVICE_CLEAR, 267 | params, 268 | self.packer.pack_device_generic_parms, 269 | self.unpacker.unpack_device_error, 270 | ) 271 | 272 | def device_remote(self, link, flags, lock_timeout, io_timeout): 273 | params = (link, flags, lock_timeout, io_timeout) 274 | return self.make_call( 275 | DEVICE_REMOTE, 276 | params, 277 | self.packer.pack_device_generic_parms, 278 | self.unpacker.unpack_device_error, 279 | ) 280 | 281 | def device_local(self, link, flags, lock_timeout, io_timeout): 282 | params = (link, flags, lock_timeout, io_timeout) 283 | return self.make_call( 284 | DEVICE_LOCAL, 285 | params, 286 | self.packer.pack_device_generic_parms, 287 | self.unpacker.unpack_device_error, 288 | ) 289 | 290 | def device_lock(self, link, flags, lock_timeout): 291 | params = (link, flags, lock_timeout) 292 | return self.make_call( 293 | DEVICE_LOCK, 294 | params, 295 | self.packer.pack_device_lock_parms, 296 | self.unpacker.unpack_device_error, 297 | ) 298 | 299 | def device_unlock(self, link): 300 | return self.make_call( 301 | DEVICE_UNLOCK, 302 | link, 303 | self.packer.pack_device_link, 304 | self.unpacker.unpack_device_error, 305 | ) 306 | 307 | def device_enable_srq(self, link, enable, handle): 308 | params = (link, enable, handle) 309 | return self.make_call( 310 | DEVICE_ENABLE_SRQ, 311 | params, 312 | self.packer.pack_device_enable_srq_parms, 313 | self.unpacker.unpack_device_error, 314 | ) 315 | 316 | def device_docmd( 317 | self, 318 | link, 319 | flags, 320 | io_timeout, 321 | lock_timeout, 322 | cmd, 323 | network_order, 324 | datasize, 325 | data_in, 326 | ): 327 | params = ( 328 | link, 329 | flags, 330 | io_timeout, 331 | lock_timeout, 332 | cmd, 333 | network_order, 334 | datasize, 335 | data_in, 336 | ) 337 | return self.make_call( 338 | DEVICE_DOCMD, 339 | params, 340 | self.packer.pack_device_docmd_parms, 341 | self.unpacker.unpack_device_docmd_resp, 342 | ) 343 | 344 | def destroy_link(self, link): 345 | return self.make_call( 346 | DESTROY_LINK, 347 | link, 348 | self.packer.pack_device_link, 349 | self.unpacker.unpack_device_error, 350 | ) 351 | 352 | def create_intr_chan(self, host_addr, host_port, prog_num, prog_vers, prog_family): 353 | params = (host_addr, host_port, prog_num, prog_vers, prog_family) 354 | return self.make_call( 355 | CREATE_INTR_CHAN, 356 | params, 357 | self.packer.pack_device_docmd_parms, 358 | self.unpacker.unpack_device_error, 359 | ) 360 | 361 | def destroy_intr_chan(self): 362 | return self.make_call( 363 | DESTROY_INTR_CHAN, None, None, self.unpacker.unpack_device_error 364 | ) 365 | -------------------------------------------------------------------------------- /pyvisa_py/prologix.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implements the interface and instrument classes for Prologix-style devices. 3 | 4 | :copyright: 2025 by PyVISA-py Authors, see AUTHORS for more details. 5 | :license: MIT, see LICENSE for more details. 6 | 7 | """ 8 | 9 | import select 10 | import socket 11 | import sys 12 | import threading 13 | from typing import Any 14 | 15 | from pyvisa import attributes, constants, errors, logger, rname 16 | from pyvisa.constants import BufferOperation, ResourceAttribute, StatusCode 17 | 18 | from .sessions import Session, UnknownAttribute, VISARMSession 19 | from .tcpip import TCPIPSocketSession 20 | 21 | # Allow to work even in the absence of pyserial 22 | try: 23 | import serial as pyserial 24 | except Exception as e: 25 | SerialSession = None 26 | comports = None 27 | pyserial = None 28 | ERR_MSG = f"{e}" 29 | else: 30 | from .serial import SerialSession, comports # type: ignore 31 | 32 | ERR_MSG = "" 33 | 34 | IS_WIN = sys.platform == "win32" 35 | 36 | 37 | class _PrologixIntfcSession(Session): # pylint: disable=W0223 38 | """ 39 | This is the common class for both 40 | PRLGX-TCPIP::INTFC resources and 41 | PRLGX-ASRL::INTFC resources. 42 | """ 43 | 44 | # class var for looking up Prologix INTFC instances from board number 45 | boards: dict = {} 46 | 47 | # Override parsed to take into account the fact that this 48 | # class is only used for specific kinds of resources 49 | parsed: rname.TCPIPSocket | rname.PrlgxASRLIntfc 50 | plus_plus_read: bool = True 51 | rd_ahead: bytes = b"" 52 | 53 | def __init__( 54 | self, 55 | resource_manager_session: VISARMSession, 56 | resource_name: str, 57 | parsed: rname.ResourceName | None = None, 58 | open_timeout: int | None = None, 59 | ) -> None: 60 | super().__init__(resource_manager_session, resource_name, parsed, open_timeout) 61 | 62 | # store this instance in the dictionary of Prologix interfaces 63 | self.boards[self.parsed.board] = self 64 | 65 | self.set_attribute(ResourceAttribute.termchar, ord("\n")) 66 | self.set_attribute(ResourceAttribute.termchar_enabled, True) 67 | 68 | # Set mode as CONTROLLER 69 | self.write_oob(b"++mode 1\n") 70 | 71 | # Turn off read-after-write to avoid "Query Unterminated" errors 72 | self.write_oob(b"++auto 0\n") 73 | 74 | # Read timeout is 50ms (from Willow Garage, Inc code) 75 | self.write_oob(b"++read_tmo_ms 50\n") 76 | 77 | # Do not append CR or LF to GPIB data 78 | self.write_oob(b"++eos 3\n") 79 | 80 | # Assert EOI with last byte to indicate end of data 81 | self.write_oob(b"++eoi 1\n") 82 | 83 | # do not append eot_char to recvd data when EOI detected 84 | self.write_oob(b"++eot_enable 0\n") 85 | 86 | self._gpib_addr = "" 87 | 88 | self.intfc_lock = threading.Lock() 89 | 90 | def close(self) -> StatusCode: 91 | try: 92 | _PrologixIntfcSession.boards.pop(self.parsed.board) 93 | except KeyError: 94 | # probably raised an exception before __init__ finished 95 | pass 96 | 97 | return super().close() # type: ignore[safe-super] 98 | 99 | @property 100 | def gpib_addr(self) -> str: 101 | """ 102 | gpib_addr is the currently addressed gpib instrument 103 | """ 104 | return self._gpib_addr 105 | 106 | @gpib_addr.setter 107 | def gpib_addr(self, addr: str) -> None: 108 | if self._gpib_addr != addr: 109 | self.write_oob(f"++addr {addr}\n".encode()) 110 | self._gpib_addr = addr 111 | 112 | def write_oob(self, data: bytes) -> tuple[int, StatusCode]: 113 | """out-of-band write (for sending "++" commands)""" 114 | if self.interface is None: 115 | raise errors.InvalidSession() 116 | 117 | return super().write(data) 118 | 119 | def read(self, count: int) -> tuple[bytes, StatusCode]: 120 | if self.interface is None: 121 | raise errors.InvalidSession() 122 | 123 | if self.plus_plus_read: 124 | self.plus_plus_read = False 125 | self.write_oob(b"++read eoi\n") 126 | 127 | return super().read(count) 128 | 129 | def assert_trigger(self, protocol: constants.TriggerProtocol) -> StatusCode: 130 | """Asserts hardware trigger. 131 | 132 | Implemented by instr sessions, not intfc sessions. 133 | """ 134 | if self.interface is None: 135 | raise errors.InvalidSession() 136 | 137 | raise NotImplementedError 138 | 139 | 140 | @Session.register(constants.InterfaceType.prlgx_tcpip, "INTFC") 141 | class PrologixTCPIPIntfcSession(_PrologixIntfcSession, TCPIPSocketSession): 142 | """Instantiated for PRLGX-TCPIP::INTFC resources.""" 143 | 144 | # Override parsed to take into account the fact that this class is only 145 | # used for specific kinds of resources 146 | parsed: rname.TCPIPSocket 147 | 148 | def write(self, data: bytes) -> tuple[int, StatusCode]: 149 | """Writes data to device or interface synchronously. 150 | 151 | Corresponds to viWrite function of the VISA library. 152 | 153 | Parameters 154 | ---------- 155 | data : bytes 156 | Data to be written. 157 | 158 | Returns 159 | ------- 160 | int 161 | Number of bytes actually transferred 162 | StatusCode 163 | Return value of the library call. 164 | 165 | """ 166 | if self.interface is None: 167 | raise errors.InvalidSession() 168 | 169 | try: 170 | # use select to wait for write ready 171 | rd, _wr, _ = select.select([self.interface], [self.interface], []) 172 | if rd: 173 | # any data that hasn't been read yet is now considered stale, 174 | # and should be discarded. 175 | self.clear() 176 | except socket.timeout: 177 | return 0, StatusCode.error_io 178 | 179 | self._pending_buffer.clear() # discard any stale unread data 180 | self.plus_plus_read = True 181 | return super().write(data) 182 | 183 | 184 | if SerialSession is not None: 185 | # Mypy is unhappy with the handling of the possibly failing import 186 | 187 | @Session.register(constants.InterfaceType.prlgx_asrl, "INTFC") 188 | class PrologixASRLIntfcSession(_PrologixIntfcSession, SerialSession): # type: ignore 189 | """Instantiated for PRLGX-ASRL::INTFC resources.""" 190 | 191 | # Override parsed to take into account the fact that this class is only 192 | # used for specific kinds of resources 193 | parsed: rname.PrlgxASRLIntfc # type: ignore[assignment] 194 | 195 | @staticmethod 196 | def list_resources() -> list[str]: 197 | return [ 198 | f"PRLGX-ASRL::{port[0][3:] if IS_WIN else port[0]}::INTFC" 199 | for port in comports() 200 | ] 201 | 202 | def after_parsing(self) -> None: 203 | self.interface = pyserial.serial_for_url( 204 | ("COM" if IS_WIN else "") + self.parsed.serial_device, 205 | timeout=self.timeout, 206 | write_timeout=self.timeout, 207 | baudrate=115200, 208 | ) 209 | 210 | for name in ( 211 | "ASRL_END_IN", 212 | "ASRL_END_OUT", 213 | "SEND_END_EN", 214 | "TERMCHAR", 215 | "TERMCHAR_EN", 216 | "SUPPRESS_END_EN", 217 | ): 218 | attribute = getattr(constants, "VI_ATTR_" + name) 219 | self.attrs[attribute] = attributes.AttributesByID[attribute].default 220 | 221 | def write(self, data: bytes) -> tuple[int, StatusCode]: 222 | """Writes data to device or interface synchronously. 223 | 224 | Corresponds to viWrite function of the VISA library. 225 | 226 | Parameters 227 | ---------- 228 | data : bytes 229 | Data to be written. 230 | 231 | Returns 232 | ------- 233 | int 234 | Number of bytes actually transferred 235 | StatusCode 236 | Return value of the library call. 237 | 238 | """ 239 | if self.interface is None: 240 | raise errors.InvalidSession() 241 | 242 | if self.interface.inWaiting() > 0: 243 | # any data that hasn't been read yet is now considered stale, 244 | # and should be discarded. 245 | self.interface.flushInput() 246 | self.plus_plus_read = True 247 | return super().write(data) 248 | else: 249 | Session.register_unavailable( 250 | constants.InterfaceType.prlgx_asrl, 251 | "INTFC", 252 | "Please install PySerial (>=3.0) to use this resource type.\n%s" % ERR_MSG, 253 | ) 254 | 255 | 256 | class PrologixInstrSession(Session): 257 | """ 258 | This class is instantiated for GPIB::INSTR resources, but only when 259 | the corresponding PRLGX-xxx::INTFC resource has been instantiated. 260 | """ 261 | 262 | # we don't decorate this class with Session.register() because we don't 263 | # want it to be registered in the _session_classes array, but we still 264 | # need to define session_type to make the set_attribute machinery work. 265 | session_type = (constants.InterfaceType.gpib, "INSTR") 266 | 267 | # Override parsed to take into account the fact that this 268 | # class is only used for a specific kind of resource 269 | parsed: rname.GPIBInstr 270 | 271 | @staticmethod 272 | def list_resources() -> list[str]: 273 | # TODO: is there a way to get this? 274 | return [] 275 | 276 | def after_parsing(self) -> None: 277 | self.interface = _PrologixIntfcSession.boards[self.parsed.board] 278 | 279 | self.gpib_addr = self.parsed.primary_address 280 | if self.parsed.secondary_address: 281 | # Secondary address of the device to connect to 282 | # Reference for the GPIB secondary address 283 | # https://www.mathworks.com/help/instrument/secondaryaddress.html 284 | # NOTE: a secondary address of 0 is not the same as no secondary address. 285 | self.gpib_addr += " " + self.parsed.secondary_address 286 | 287 | def close(self) -> StatusCode: 288 | if self.interface is None or self.interface.interface is None: 289 | raise errors.InvalidSession() 290 | 291 | self.interface = None 292 | return StatusCode.success 293 | 294 | def read(self, count: int) -> tuple[bytes, StatusCode]: 295 | if self.interface is None or self.interface.interface is None: 296 | raise errors.InvalidSession() 297 | 298 | with self.interface.intfc_lock: 299 | self.interface.gpib_addr = self.gpib_addr 300 | return self.interface.read(count) 301 | 302 | def write(self, data: bytes) -> tuple[int, StatusCode]: 303 | if self.interface is None or self.interface.interface is None: 304 | raise errors.InvalidSession() 305 | 306 | # if the calling function has appended a newline to the data, 307 | # we don't want it to be escaped. remove it from the data 308 | # and stash it away so we can append it after all the escapes 309 | # have been added in. 310 | if data[-2:] == b"\r\n": 311 | last_byte = b"\r\n" 312 | data = data[:-2] 313 | elif data[-2:] == b"\n\r": 314 | last_byte = b"\n\r" 315 | data = data[:-2] 316 | elif data[-1] == ord("\n"): 317 | last_byte = b"\n" 318 | data = data[:-1] 319 | else: 320 | last_byte = b"" 321 | 322 | # escape the "special" characters 323 | data = data.replace(b"\033", b"\033\033") 324 | data = data.replace(b"\n", b"\033\n") 325 | data = data.replace(b"\r", b"\033\r") 326 | data = data.replace(b"+", b"\033+") 327 | 328 | with self.interface.intfc_lock: 329 | self.interface.gpib_addr = self.gpib_addr 330 | return self.interface.write(data + last_byte) 331 | 332 | def flush(self, mask: BufferOperation) -> StatusCode: 333 | if self.interface is None or self.interface.interface is None: 334 | raise errors.InvalidSession() 335 | 336 | return self.interface.flush(mask) 337 | 338 | def clear(self) -> StatusCode: 339 | """Clears a device. 340 | 341 | Corresponds to viClear function of the VISA library. 342 | 343 | Returns 344 | ------- 345 | StatusCode 346 | Return value of the library call. 347 | 348 | """ 349 | logger.debug("GPIB.device clear") 350 | if self.interface is None or self.interface.interface is None: 351 | raise errors.InvalidSession() 352 | 353 | with self.interface.intfc_lock: 354 | self.interface.gpib_addr = self.gpib_addr 355 | _, status_code = self.interface.write_oob(b"++clr\n") 356 | 357 | return status_code 358 | 359 | def assert_trigger(self, protocol: constants.TriggerProtocol) -> StatusCode: 360 | """Asserts hardware trigger. 361 | 362 | Parameters 363 | ---------- 364 | protocol : constants.TriggerProtocol 365 | Triggering protocol to use. 366 | Only supports constants.TriggerProtocol.default 367 | 368 | Returns 369 | ------- 370 | StatusCode 371 | Return value of the library call. 372 | 373 | """ 374 | logger.debug("GPIB.device assert hardware trigger") 375 | 376 | if self.interface is None or self.interface.interface is None: 377 | raise errors.InvalidSession() 378 | 379 | with self.interface.intfc_lock: 380 | self.interface.gpib_addr = self.gpib_addr 381 | _, status_code = self.interface.write_oob(b"++trg\n") 382 | 383 | return status_code 384 | 385 | def read_stb(self) -> tuple[int, StatusCode]: 386 | """Read the device status byte.""" 387 | if self.interface is None or self.interface.interface is None: 388 | raise errors.InvalidSession() 389 | 390 | with self.interface.intfc_lock: 391 | self.interface.gpib_addr = self.gpib_addr 392 | self.interface.write_oob(b"++spoll\n") 393 | data, status_code = self.interface.read(32) 394 | 395 | return (int(data), status_code) 396 | 397 | def _get_attribute(self, attribute: ResourceAttribute) -> tuple[Any, StatusCode]: 398 | """Get the value for a given VISA attribute for this session. 399 | 400 | Use to implement custom logic for attributes. 401 | 402 | Parameters 403 | ---------- 404 | attribute : ResourceAttribute 405 | Attribute for which the state query is made 406 | 407 | Returns 408 | ------- 409 | Any 410 | State of the queried attribute for a specified resource 411 | StatusCode 412 | Return value of the library call. 413 | 414 | """ 415 | raise UnknownAttribute(attribute) 416 | 417 | def _set_attribute( 418 | self, attribute: ResourceAttribute, attribute_state: Any 419 | ) -> StatusCode: 420 | """Sets the state of an attribute. 421 | 422 | Corresponds to viSetAttribute function of the VISA library. 423 | 424 | Parameters 425 | ---------- 426 | attribute : constants.ResourceAttribute 427 | Attribute for which the state is to be modified. (Attributes.*) 428 | attribute_state : Any 429 | The state of the attribute to be set for the specified object. 430 | 431 | Returns 432 | ------- 433 | StatusCode 434 | Return value of the library call. 435 | 436 | """ 437 | raise UnknownAttribute(attribute) 438 | -------------------------------------------------------------------------------- /pyvisa_py/usb.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Serial Session implementation using PyUSB. 3 | 4 | 5 | :copyright: 2014-2025 by PyVISA-py Authors, see AUTHORS for more details. 6 | :license: MIT, see LICENSE for more details. 7 | 8 | """ 9 | 10 | import errno 11 | import logging 12 | import os 13 | import sys 14 | import traceback 15 | from typing import Any, List, Tuple, Type, Union 16 | 17 | from pyvisa import attributes, constants 18 | from pyvisa.constants import ResourceAttribute, StatusCode 19 | from pyvisa.rname import USBInstr, USBRaw 20 | 21 | from .common import LOGGER 22 | from .sessions import Session, UnknownAttribute 23 | 24 | try: 25 | import usb 26 | 27 | from .protocols import usbraw, usbtmc, usbutil 28 | except ImportError as e: 29 | msg = "Please install PyUSB to use this resource type.\n%s" 30 | Session.register_unavailable(constants.InterfaceType.usb, "INSTR", msg % e) 31 | Session.register_unavailable(constants.InterfaceType.usb, "RAW", msg % e) 32 | raise 33 | 34 | try: 35 | _ = usb.core.find() 36 | except Exception as e1: 37 | try: 38 | import libusb_package 39 | 40 | _ = libusb_package.find() 41 | except Exception as e2: 42 | msg = ( 43 | "PyUSB does not seem to be properly installed.\n" 44 | "Please refer to the PyUSB documentation and \n" 45 | "install a suitable backend like \n" 46 | "libusb 0.1, libusb 1.0, libusbx, \n" 47 | "libusb-win32 or OpenUSB. If you do not have \n" 48 | "administrator/root privileges, you may try \n" 49 | 'installing the "libusb-package" Python \n' 50 | "package to provide the necessary backend.\n%s\n%s" % (e2, e1) 51 | ) 52 | Session.register_unavailable(constants.InterfaceType.usb, "INSTR", msg) 53 | Session.register_unavailable(constants.InterfaceType.usb, "RAW", msg) 54 | raise 55 | 56 | 57 | class USBTimeoutException(Exception): 58 | """Exception used internally to indicate USB timeout.""" 59 | 60 | 61 | class USBSession(Session): 62 | """Base class for drivers working with usb devices via usb port using pyUSB.""" 63 | 64 | # Override parsed to take into account the fact that this class is only used 65 | # for a specific kind of resource 66 | parsed: Union[USBInstr, USBRaw] 67 | 68 | #: Class to use when instantiating the interface 69 | _intf_cls: Union[Type[usbraw.USBRawDevice], Type[usbtmc.USBTMC]] 70 | 71 | @staticmethod 72 | def list_resources() -> List[str]: 73 | """Return list of resources for this type of USB device.""" 74 | raise NotImplementedError 75 | 76 | @classmethod 77 | def get_low_level_info(cls) -> str: 78 | try: 79 | ver = usb.__version__ 80 | except AttributeError: 81 | ver = "N/A" 82 | 83 | try: 84 | # noinspection PyProtectedMember 85 | backend = usb.core.find()._ctx.backend.__class__.__module__.split(".")[-1] 86 | except Exception: 87 | try: 88 | backend = libusb_package.find()._ctx.backend.__class__.__module__.split( 89 | "." 90 | )[-1] 91 | except Exception: 92 | backend = "N/A" 93 | 94 | return "via PyUSB (%s). Backend: %s" % (ver, backend) 95 | 96 | def after_parsing(self) -> None: 97 | self.interface = self._intf_cls( 98 | int(self.parsed.manufacturer_id, 0), 99 | int(self.parsed.model_code, 0), 100 | self.parsed.serial_number, 101 | ) 102 | 103 | self.attrs.update( 104 | { 105 | ResourceAttribute.manufacturer_id: int(self.parsed.manufacturer_id, 0), 106 | ResourceAttribute.model_code: int(self.parsed.model_code, 0), 107 | ResourceAttribute.usb_serial_number: self.parsed.serial_number, 108 | ResourceAttribute.usb_interface_number: int( 109 | self.parsed.usb_interface_number 110 | ), 111 | } 112 | ) 113 | 114 | for name, attr in ( 115 | ("SEND_END_EN", ResourceAttribute.send_end_enabled), 116 | ("SUPPRESS_END_EN", ResourceAttribute.suppress_end_enabled), 117 | ("TERMCHAR", ResourceAttribute.termchar), 118 | ("TERMCHAR_EN", ResourceAttribute.termchar_enabled), 119 | ): 120 | attribute = getattr(constants, "VI_ATTR_" + name) 121 | self.attrs[attr] = attributes.AttributesByID[attribute].default 122 | 123 | # Force setting the timeout to get the proper value 124 | self.set_attribute( 125 | ResourceAttribute.timeout_value, 126 | attributes.AttributesByID[attribute].default, 127 | ) 128 | 129 | def _get_timeout(self, attribute: ResourceAttribute) -> Tuple[int, StatusCode]: 130 | if self.interface: 131 | if self.interface.timeout == 2**32 - 1: 132 | self.timeout = None 133 | else: 134 | self.timeout = self.interface.timeout / 1000 135 | return super(USBSession, self)._get_timeout(attribute) 136 | 137 | def _set_timeout(self, attribute: ResourceAttribute, value: int) -> StatusCode: 138 | status = super(USBSession, self)._set_timeout(attribute, value) 139 | timeout = int(self.timeout * 1000) if self.timeout else 2**32 - 1 140 | timeout = min(timeout, 2**32 - 1) 141 | if self.interface: 142 | self.interface.timeout = timeout 143 | return status 144 | 145 | def read(self, count: int) -> Tuple[bytes, StatusCode]: 146 | """Reads data from device or interface synchronously. 147 | 148 | Corresponds to viRead function of the VISA library. 149 | 150 | Parameters 151 | ----------- 152 | count : int 153 | Number of bytes to be read. 154 | 155 | Returns 156 | ------- 157 | bytes 158 | Data read from the device 159 | StatusCode 160 | Return value of the library call. 161 | 162 | """ 163 | 164 | def _usb_reader(): 165 | """Data reader identifying usb timeout exception.""" 166 | try: 167 | return self.interface.read(count) 168 | except usb.USBError as exc: 169 | if exc.errno in (errno.ETIMEDOUT, -errno.ETIMEDOUT): 170 | raise USBTimeoutException() 171 | raise 172 | 173 | supress_end_en, _ = self.get_attribute(ResourceAttribute.suppress_end_enabled) 174 | 175 | term_char, _ = self.get_attribute(ResourceAttribute.termchar) 176 | term_char_en, _ = self.get_attribute(ResourceAttribute.termchar_enabled) 177 | 178 | return self._read( 179 | _usb_reader, 180 | count, 181 | lambda current: True, # USB always returns a complete message 182 | supress_end_en, 183 | term_char, 184 | term_char_en, 185 | USBTimeoutException, 186 | ) 187 | 188 | def write(self, data: bytes) -> Tuple[int, StatusCode]: 189 | """Writes data to device or interface synchronously. 190 | 191 | Corresponds to viWrite function of the VISA library. 192 | 193 | Parameters 194 | ---------- 195 | data : bytes 196 | Data to be written. 197 | 198 | Returns 199 | ------- 200 | int 201 | Number of bytes actually transferred 202 | StatusCode 203 | Return value of the library call. 204 | 205 | """ 206 | _send_end, _ = self.get_attribute(ResourceAttribute.send_end_enabled) 207 | 208 | count = self.interface.write(data) 209 | 210 | return count, StatusCode.success 211 | 212 | def read_stb(self) -> Tuple[int, StatusCode]: 213 | """Reads a status byte of the service request. 214 | 215 | Returns 216 | ------- 217 | int 218 | Service request status byte 219 | StatusCode 220 | Return value of the library call. 221 | 222 | """ 223 | if hasattr(self.interface, "read_stb"): 224 | return self.interface.read_stb() 225 | return 0, StatusCode.error_nonsupported_operation 226 | 227 | def assert_trigger(self, protocol: constants.TriggerProtocol) -> StatusCode: 228 | """Assert software or hardware trigger. 229 | 230 | Corresponds to viAssertTrigger function of the VISA library. 231 | 232 | Parameters 233 | ---------- 234 | protocol : constants.TriggerProtocol 235 | Trigger protocol to use during assertion. 236 | 237 | Returns 238 | ------- 239 | StatusCode 240 | Return value of the library call. 241 | 242 | """ 243 | if hasattr(self.interface, "assert_trigger"): 244 | return self.interface.assert_trigger(protocol) 245 | return StatusCode.error_nonsupported_operation 246 | 247 | def gpib_control_ren(self, mode: constants.RENLineOperation) -> StatusCode: 248 | """Controls the state of the GPIB Remote Enable (REN) interface line. 249 | 250 | Optionally the remote/local state of the device can also be set. 251 | 252 | Corresponds to viGpibControlREN function of the VISA library. 253 | 254 | Parameters 255 | ---------- 256 | mode : constants.RENLineOperation 257 | State of the REN line and optionally the device remote/local state. 258 | 259 | Returns 260 | ------- 261 | StatusCode 262 | Return value of the library call. 263 | 264 | """ 265 | if hasattr(self.interface, "gpib_control_ren"): 266 | return self.interface.gpib_control_ren(mode) 267 | return StatusCode.error_nonsupported_operation 268 | 269 | def close(self): 270 | self.interface.close() 271 | return StatusCode.success 272 | 273 | def _get_attribute( 274 | self, attribute: constants.ResourceAttribute 275 | ) -> Tuple[Any, StatusCode]: 276 | """Get the value for a given VISA attribute for this session. 277 | 278 | Use to implement custom logic for attributes. 279 | 280 | Parameters 281 | ---------- 282 | attribute : ResourceAttribute 283 | Attribute for which the state query is made 284 | 285 | Returns 286 | ------- 287 | Any 288 | State of the queried attribute for a specified resource 289 | StatusCode 290 | Return value of the library call. 291 | 292 | """ 293 | raise UnknownAttribute(attribute) 294 | 295 | def _set_attribute( 296 | self, attribute: constants.ResourceAttribute, attribute_state: Any 297 | ) -> StatusCode: 298 | """Sets the state of an attribute. 299 | 300 | Corresponds to viSetAttribute function of the VISA library. 301 | 302 | Parameters 303 | ---------- 304 | attribute : constants.ResourceAttribute 305 | Attribute for which the state is to be modified. (Attributes.*) 306 | attribute_state : Any 307 | The state of the attribute to be set for the specified object. 308 | 309 | Returns 310 | ------- 311 | StatusCode 312 | Return value of the library call. 313 | 314 | """ 315 | raise UnknownAttribute(attribute) 316 | 317 | 318 | @Session.register(constants.InterfaceType.usb, "INSTR") 319 | class USBInstrSession(USBSession): 320 | """Class for USBTMC devices.""" 321 | 322 | # Override parsed to take into account the fact that this class is only used 323 | # for a specific kind of resource 324 | parsed: USBInstr 325 | 326 | #: Class to use when instantiating the interface 327 | _intf_cls = usbtmc.USBTMC 328 | 329 | @staticmethod 330 | def list_resources() -> List[str]: 331 | out = [] 332 | fmt = ( 333 | "USB%(board)s::%(manufacturer_id)s::%(model_code)s::" 334 | "%(serial_number)s::%(usb_interface_number)s::INSTR" 335 | ) 336 | for dev in usbtmc.find_tmc_devices(): 337 | intfc = usbutil.find_interfaces( 338 | dev, bInterfaceClass=0xFE, bInterfaceSubClass=3 339 | ) 340 | try: 341 | intfc = intfc[0].index 342 | except (IndexError, AttributeError): 343 | intfc = 0 344 | 345 | try: 346 | serial = dev.serial_number 347 | except (NotImplementedError, ValueError) as err: 348 | msg = ( 349 | "Found a USB INSTR device whose serial number cannot be read." 350 | " The partial VISA resource name is: " + fmt 351 | ) 352 | LOGGER.warning( 353 | msg, 354 | { 355 | "board": 0, 356 | "manufacturer_id": dev.idVendor, 357 | "model_code": dev.idProduct, 358 | "serial_number": "???", 359 | "usb_interface_number": intfc, 360 | }, 361 | ) 362 | logging_level = LOGGER.getEffectiveLevel() 363 | if logging_level <= logging.DEBUG: 364 | LOGGER.debug("Error while reading serial number", exc_info=err) 365 | elif logging_level <= logging.INFO: 366 | if exc_strs := traceback.format_exception_only(err): 367 | LOGGER.info( 368 | "Error raised from underlying module (pyusb): %s", 369 | exc_strs[0].strip(), 370 | ) 371 | 372 | # Check permissions on Linux 373 | if sys.platform.startswith("linux"): 374 | dev_path = f"/dev/bus/usb/{dev.bus:03d}/{dev.address:03d}" 375 | if os.path.exists(dev_path) and not os.access(dev_path, os.O_RDWR): 376 | missing_perms = [] 377 | if not os.access(dev_path, os.O_RDONLY): 378 | missing_perms.append("read from") 379 | if not os.access(dev_path, os.O_WRONLY): 380 | missing_perms.append("write to") 381 | missing_perms_str = " or ".join(missing_perms) 382 | LOGGER.warning( 383 | "User does not have permission to %s %s, so the above " 384 | "USB INSTR device cannot be used by pyvisa; see" 385 | " https://pyvisa.readthedocs.io/projects/pyvisa-py/en/latest/installation.html" 386 | " for more info.", 387 | missing_perms_str, 388 | dev_path, 389 | ) 390 | 391 | continue 392 | 393 | out.append( 394 | fmt 395 | % { 396 | "board": 0, 397 | "manufacturer_id": dev.idVendor, 398 | "model_code": dev.idProduct, 399 | "serial_number": serial, 400 | "usb_interface_number": intfc, 401 | } 402 | ) 403 | return out 404 | 405 | 406 | @Session.register(constants.InterfaceType.usb, "RAW") 407 | class USBRawSession(USBSession): 408 | """Class for RAW devices.""" 409 | 410 | # Override parsed to take into account the fact that this class is only used 411 | # for a specific kind of resource 412 | parsed: USBRaw 413 | 414 | #: Class to use when instantiating the interface 415 | _intf_cls = usbraw.USBRawDevice 416 | 417 | @staticmethod 418 | def list_resources() -> List[str]: 419 | out = [] 420 | fmt = ( 421 | "USB%(board)s::%(manufacturer_id)s::%(model_code)s::" 422 | "%(serial_number)s::%(usb_interface_number)s::RAW" 423 | ) 424 | for dev in usbraw.find_raw_devices(): 425 | intfc = usbutil.find_interfaces(dev, bInterfaceClass=0xFF) 426 | try: 427 | intfc = intfc[0].index 428 | except (IndexError, AttributeError): 429 | intfc = 0 430 | 431 | try: 432 | serial = dev.serial_number 433 | except (NotImplementedError, ValueError, usb.USBError): 434 | msg = ( 435 | "Found a USB RAW device whose serial number cannot be read." 436 | " The partial VISA resource name is: " + fmt 437 | ) 438 | LOGGER.warning( 439 | msg, 440 | { 441 | "board": 0, 442 | "manufacturer_id": dev.idVendor, 443 | "model_code": dev.idProduct, 444 | "serial_number": "???", 445 | "usb_interface_number": intfc, 446 | }, 447 | ) 448 | continue 449 | 450 | out.append( 451 | fmt 452 | % { 453 | "board": 0, 454 | "manufacturer_id": dev.idVendor, 455 | "model_code": dev.idProduct, 456 | "serial_number": serial, 457 | "usb_interface_number": intfc, 458 | } 459 | ) 460 | return out 461 | -------------------------------------------------------------------------------- /pyvisa_py/serial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Serial Session implementation using PySerial. 3 | 4 | 5 | :copyright: 2014-2024 by PyVISA-py Authors, see AUTHORS for more details. 6 | :license: MIT, see LICENSE for more details. 7 | 8 | """ 9 | 10 | import sys 11 | from typing import Any, List, Tuple 12 | 13 | from pyvisa import attributes, constants, rname 14 | from pyvisa.constants import ( 15 | BufferOperation, 16 | ResourceAttribute, 17 | SerialTermination, 18 | StatusCode, 19 | ) 20 | 21 | from . import common 22 | from .common import LOGGER 23 | from .sessions import Session, UnknownAttribute 24 | 25 | try: 26 | import serial 27 | from serial.tools.list_ports import comports 28 | except ImportError as e: 29 | Session.register_unavailable( 30 | constants.InterfaceType.asrl, 31 | "INSTR", 32 | "Please install PySerial (>=3.0) to use this resource type.\n%s" % e, 33 | ) 34 | raise 35 | 36 | IS_WIN = sys.platform == "win32" 37 | 38 | 39 | def to_state(boolean_input: bool) -> constants.LineState: 40 | """Convert a boolean input into a LineState value.""" 41 | if boolean_input: 42 | return constants.LineState.asserted 43 | return constants.LineState.unasserted 44 | 45 | 46 | @Session.register(constants.InterfaceType.asrl, "INSTR") 47 | class SerialSession(Session): 48 | """A serial Session that uses PySerial to do the low level communication.""" 49 | 50 | # Override parsed to take into account the fact that this class is only used 51 | # for a specific kind of resource 52 | parsed: rname.ASRLInstr 53 | 54 | @staticmethod 55 | def list_resources() -> List[str]: 56 | return [ 57 | "ASRL%s::INSTR" % (port[0][3:] if IS_WIN else port[0]) 58 | for port in comports() 59 | ] 60 | 61 | @classmethod 62 | def get_low_level_info(cls) -> str: 63 | try: 64 | ver = serial.VERSION 65 | except AttributeError: 66 | ver = "N/A" 67 | 68 | return "via PySerial (%s)" % ver 69 | 70 | def after_parsing(self) -> None: 71 | self.interface = serial.serial_for_url( 72 | ("COM" if IS_WIN else "") + self.parsed.board, 73 | timeout=self.timeout, 74 | write_timeout=self.timeout, 75 | ) 76 | 77 | for name in ( 78 | "ASRL_END_IN", 79 | "ASRL_END_OUT", 80 | "SEND_END_EN", 81 | "TERMCHAR", 82 | "TERMCHAR_EN", 83 | "SUPPRESS_END_EN", 84 | ): 85 | attribute = getattr(constants, "VI_ATTR_" + name) 86 | self.attrs[attribute] = attributes.AttributesByID[attribute].default 87 | 88 | def _get_timeout(self, attribute: ResourceAttribute) -> Tuple[int, StatusCode]: 89 | if self.interface: 90 | self.timeout = self.interface.timeout 91 | return super(SerialSession, self)._get_timeout(attribute) 92 | 93 | def _set_timeout(self, attribute: ResourceAttribute, value: int) -> StatusCode: 94 | status = super(SerialSession, self)._set_timeout(attribute, value) 95 | if self.interface: 96 | self.interface.timeout = self.timeout 97 | self.interface.write_timeout = self.timeout 98 | return status 99 | 100 | def close(self) -> StatusCode: 101 | self.interface.close() 102 | return StatusCode.success 103 | 104 | def read(self, count: int) -> Tuple[bytes, StatusCode]: 105 | """Reads data from device or interface synchronously. 106 | 107 | Corresponds to viRead function of the VISA library. 108 | 109 | Parameters 110 | ----------- 111 | count : int 112 | Number of bytes to be read. 113 | 114 | Returns 115 | ------- 116 | bytes 117 | Data read from the device 118 | StatusCode 119 | Return value of the library call. 120 | 121 | """ 122 | end_in, _ = self.get_attribute(ResourceAttribute.asrl_end_in) 123 | suppress_end_en, _ = self.get_attribute(ResourceAttribute.suppress_end_enabled) 124 | 125 | reader = lambda: self.interface.read(1) # noqa: E731 126 | 127 | if end_in == SerialTermination.none: 128 | checker = lambda current: False # noqa: E731 129 | 130 | elif end_in == SerialTermination.last_bit: 131 | mask = 2**self.interface.bytesize 132 | checker = lambda current: bool(current[-1] & mask) # noqa: E731 133 | 134 | elif end_in == SerialTermination.termination_char: 135 | end_char, _ = self.get_attribute(ResourceAttribute.termchar) 136 | 137 | checker = lambda current: current[-1] == end_char # noqa: E731 138 | 139 | else: 140 | raise ValueError("Unknown value for VI_ATTR_ASRL_END_IN: %s" % end_in) 141 | 142 | return self._read( 143 | reader, 144 | count, 145 | checker, 146 | suppress_end_en, 147 | None, 148 | False, 149 | serial.SerialTimeoutException, 150 | ) 151 | 152 | def write(self, data: bytes) -> Tuple[int, StatusCode]: 153 | """Writes data to device or interface synchronously. 154 | 155 | Corresponds to viWrite function of the VISA library. 156 | 157 | Parameters 158 | ---------- 159 | data : bytes 160 | Data to be written. 161 | 162 | Returns 163 | ------- 164 | int 165 | Number of bytes actually transferred 166 | StatusCode 167 | Return value of the library call. 168 | 169 | """ 170 | LOGGER.debug("Serial.write %r" % data) 171 | send_end, _ = self.get_attribute(ResourceAttribute.send_end_enabled) 172 | end_out, _ = self.get_attribute(ResourceAttribute.asrl_end_out) 173 | data_bits, _ = self.get_attribute(constants.ResourceAttribute.asrl_data_bits) 174 | 175 | if end_out == SerialTermination.none: 176 | pass 177 | elif end_out == SerialTermination.last_bit: 178 | data = b"".join(common.iter_bytes(data, data_bits, send_end)) 179 | elif end_out == SerialTermination.termination_char: 180 | term_char, _ = self.get_attribute(ResourceAttribute.termchar) 181 | data = b"".join(common.iter_bytes(data, data_bits, send_end=None)) 182 | data = data + common.int_to_byte(term_char) 183 | elif end_out == SerialTermination.termination_break: 184 | data = b"".join(common.iter_bytes(data, data_bits, send_end=None)) 185 | else: 186 | raise ValueError("Unknown value for VI_ATTR_ASRL_END_OUT: %s" % end_out) 187 | 188 | try: 189 | count = self.interface.write(data) 190 | 191 | if end_out == SerialTermination.termination_break: 192 | LOGGER.debug("Serial.sendBreak") 193 | self.interface.sendBreak() 194 | 195 | return count, StatusCode.success 196 | 197 | except serial.SerialTimeoutException: 198 | return 0, StatusCode.error_timeout 199 | 200 | def flush(self, mask: BufferOperation) -> StatusCode: 201 | """Flush the specified buffers. 202 | 203 | The buffers can be associated with formatted I/O operations and/or 204 | serial communication. 205 | 206 | Corresponds to viFlush function of the VISA library. 207 | 208 | Parameters 209 | ---------- 210 | mask : constants.BufferOperation 211 | Specifies the action to be taken with flushing the buffer. 212 | The values can be combined using the | operator. However multiple 213 | operations on a single buffer cannot be combined. 214 | 215 | Returns 216 | ------- 217 | constants.StatusCode 218 | Return value of the library call. 219 | 220 | """ 221 | if mask & BufferOperation.discard_read_buffer: 222 | self.interface.reset_input_buffer() 223 | if ( 224 | mask & BufferOperation.flush_write_buffer 225 | or mask & BufferOperation.flush_transmit_buffer 226 | ): 227 | self.interface.flush() 228 | if ( 229 | mask & BufferOperation.discard_write_buffer 230 | or mask & BufferOperation.discard_transmit_buffer 231 | ): 232 | self.interface.reset_output_buffer() 233 | 234 | return StatusCode.success 235 | 236 | def _get_attribute( # noqa: C901 237 | self, attribute: constants.ResourceAttribute 238 | ) -> Tuple[Any, StatusCode]: 239 | """Get the value for a given VISA attribute for this session. 240 | 241 | Use to implement custom logic for attributes. 242 | 243 | Parameters 244 | ---------- 245 | attribute : ResourceAttribute 246 | Attribute for which the state query is made 247 | 248 | Returns 249 | ------- 250 | Any 251 | State of the queried attribute for a specified resource 252 | StatusCode 253 | Return value of the library call. 254 | 255 | """ 256 | if attribute == constants.VI_ATTR_ASRL_ALLOW_TRANSMIT: 257 | raise NotImplementedError 258 | 259 | elif attribute == constants.VI_ATTR_ASRL_AVAIL_NUM: 260 | return self.interface.inWaiting(), StatusCode.success 261 | 262 | elif attribute == constants.VI_ATTR_ASRL_BAUD: 263 | return self.interface.baudrate, StatusCode.success 264 | 265 | elif attribute == constants.VI_ATTR_ASRL_BREAK_LEN: 266 | raise NotImplementedError 267 | 268 | elif attribute == constants.VI_ATTR_ASRL_BREAK_STATE: 269 | raise NotImplementedError 270 | 271 | elif attribute == constants.VI_ATTR_ASRL_CONNECTED: 272 | raise NotImplementedError 273 | 274 | elif attribute == constants.VI_ATTR_ASRL_CTS_STATE: 275 | return to_state(self.interface.getCTS()), StatusCode.success 276 | 277 | elif attribute == constants.VI_ATTR_ASRL_DATA_BITS: 278 | return self.interface.bytesize, StatusCode.success 279 | 280 | elif attribute == constants.VI_ATTR_ASRL_DCD_STATE: 281 | raise NotImplementedError 282 | 283 | elif attribute == constants.VI_ATTR_ASRL_DISCARD_NULL: 284 | raise NotImplementedError 285 | 286 | elif attribute == constants.VI_ATTR_ASRL_DSR_STATE: 287 | return to_state(self.interface.getDSR()), StatusCode.success 288 | 289 | elif attribute == constants.VI_ATTR_ASRL_DTR_STATE: 290 | raise NotImplementedError 291 | 292 | elif attribute == constants.VI_ATTR_ASRL_FLOW_CNTRL: 293 | return ( 294 | ( 295 | self.interface.xonxoff * constants.VI_ASRL_FLOW_XON_XOFF 296 | | self.interface.rtscts * constants.VI_ASRL_FLOW_RTS_CTS 297 | | self.interface.dsrdtr * constants.VI_ASRL_FLOW_DTR_DSR 298 | ), 299 | StatusCode.success, 300 | ) 301 | 302 | elif attribute == constants.VI_ATTR_ASRL_PARITY: 303 | parity = self.interface.parity 304 | if parity == serial.PARITY_NONE: 305 | return constants.Parity.none, StatusCode.success 306 | elif parity == serial.PARITY_EVEN: 307 | return constants.Parity.even, StatusCode.success 308 | elif parity == serial.PARITY_ODD: 309 | return constants.Parity.odd, StatusCode.success 310 | elif parity == serial.PARITY_MARK: 311 | return constants.Parity.mark, StatusCode.success 312 | elif parity == serial.PARITY_SPACE: 313 | return constants.Parity.space, StatusCode.success 314 | 315 | raise Exception("Unknown parity value: %r" % parity) 316 | 317 | elif attribute == constants.VI_ATTR_ASRL_RI_STATE: 318 | raise NotImplementedError 319 | 320 | elif attribute == constants.VI_ATTR_ASRL_RTS_STATE: 321 | raise NotImplementedError 322 | 323 | elif attribute == constants.VI_ATTR_ASRL_STOP_BITS: 324 | bits = self.interface.stopbits 325 | if bits == serial.STOPBITS_ONE: 326 | return constants.StopBits.one, StatusCode.success 327 | elif bits == serial.STOPBITS_ONE_POINT_FIVE: 328 | return constants.StopBits.one_and_a_half, StatusCode.success 329 | elif bits == serial.STOPBITS_TWO: 330 | return constants.StopBits.two, StatusCode.success 331 | 332 | raise Exception("Unknown bits value: %r" % bits) 333 | 334 | elif attribute == constants.VI_ATTR_ASRL_XOFF_CHAR: 335 | raise NotImplementedError 336 | 337 | elif attribute == constants.VI_ATTR_INTF_TYPE: 338 | return constants.InterfaceType.asrl, StatusCode.success 339 | 340 | raise UnknownAttribute(attribute) 341 | 342 | def _set_attribute( # noqa: C901 343 | self, attribute: constants.ResourceAttribute, attribute_state: Any 344 | ) -> StatusCode: 345 | """Sets the state of an attribute. 346 | 347 | Corresponds to viSetAttribute function of the VISA library. 348 | 349 | Parameters 350 | ---------- 351 | attribute : constants.ResourceAttribute 352 | Attribute for which the state is to be modified. (Attributes.*) 353 | attribute_state : Any 354 | The state of the attribute to be set for the specified object. 355 | 356 | Returns 357 | ------- 358 | StatusCode 359 | Return value of the library call. 360 | 361 | """ 362 | if attribute == constants.VI_ATTR_ASRL_ALLOW_TRANSMIT: 363 | raise NotImplementedError 364 | 365 | elif attribute == constants.VI_ATTR_ASRL_BAUD: 366 | self.interface.baudrate = attribute_state 367 | return StatusCode.success 368 | 369 | elif attribute == constants.VI_ATTR_ASRL_BREAK_LEN: 370 | raise NotImplementedError 371 | 372 | elif attribute == constants.VI_ATTR_ASRL_BREAK_STATE: 373 | raise NotImplementedError 374 | 375 | elif attribute == constants.VI_ATTR_ASRL_CONNECTED: 376 | raise NotImplementedError 377 | 378 | elif attribute == constants.VI_ATTR_ASRL_DATA_BITS: 379 | self.interface.bytesize = attribute_state 380 | return StatusCode.success 381 | 382 | elif attribute == constants.VI_ATTR_ASRL_DCD_STATE: 383 | raise NotImplementedError 384 | 385 | elif attribute == constants.VI_ATTR_ASRL_DISCARD_NULL: 386 | raise NotImplementedError 387 | 388 | elif attribute == constants.VI_ATTR_ASRL_DSR_STATE: 389 | raise NotImplementedError 390 | 391 | elif attribute == constants.VI_ATTR_ASRL_DTR_STATE: 392 | raise NotImplementedError 393 | 394 | elif attribute == constants.VI_ATTR_ASRL_FLOW_CNTRL: 395 | if not isinstance(attribute_state, int): 396 | return StatusCode.error_nonsupported_attribute_state 397 | 398 | if not 0 <= attribute_state < 8: 399 | return StatusCode.error_nonsupported_attribute_state 400 | 401 | try: 402 | self.interface.xonxoff = bool( 403 | attribute_state & constants.VI_ASRL_FLOW_XON_XOFF 404 | ) 405 | self.interface.rtscts = bool( 406 | attribute_state & constants.VI_ASRL_FLOW_RTS_CTS 407 | ) 408 | self.interface.dsrdtr = bool( 409 | attribute_state & constants.VI_ASRL_FLOW_DTR_DSR 410 | ) 411 | return StatusCode.success 412 | except Exception: 413 | return StatusCode.error_nonsupported_attribute_state 414 | 415 | elif attribute == constants.VI_ATTR_ASRL_PARITY: 416 | if attribute_state == constants.Parity.none: 417 | self.interface.parity = serial.PARITY_NONE 418 | return StatusCode.success 419 | 420 | elif attribute_state == constants.Parity.even: 421 | self.interface.parity = serial.PARITY_EVEN 422 | return StatusCode.success 423 | 424 | elif attribute_state == constants.Parity.odd: 425 | self.interface.parity = serial.PARITY_ODD 426 | return StatusCode.success 427 | 428 | elif attribute_state == serial.PARITY_MARK: 429 | self.interface.parity = serial.PARITY_MARK 430 | return StatusCode.success 431 | 432 | elif attribute_state == constants.Parity.space: 433 | self.interface.parity = serial.PARITY_SPACE 434 | return StatusCode.success 435 | 436 | return StatusCode.error_nonsupported_attribute_state 437 | 438 | elif attribute == constants.VI_ATTR_ASRL_RI_STATE: 439 | raise NotImplementedError 440 | 441 | elif attribute == constants.VI_ATTR_ASRL_RTS_STATE: 442 | raise NotImplementedError 443 | 444 | elif attribute == constants.VI_ATTR_ASRL_STOP_BITS: 445 | if attribute_state == constants.StopBits.one: 446 | self.interface.stopbits = serial.STOPBITS_ONE 447 | return StatusCode.success 448 | 449 | if attribute_state == constants.StopBits.one_and_a_half: 450 | self.interface.stopbits = serial.STOPBITS_ONE_POINT_FIVE 451 | return StatusCode.success 452 | 453 | if attribute_state == constants.StopBits.two: 454 | self.interface.stopbits = serial.STOPBITS_TWO 455 | return StatusCode.success 456 | 457 | return StatusCode.error_nonsupported_attribute_state 458 | 459 | elif attribute == constants.VI_ATTR_ASRL_XOFF_CHAR: 460 | raise NotImplementedError 461 | 462 | raise UnknownAttribute(attribute) 463 | -------------------------------------------------------------------------------- /pyvisa_py/highlevel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Highlevel wrapper of the VISA Library. 3 | 4 | 5 | :copyright: 2014-2024 by PyVISA-py Authors, see AUTHORS for more details. 6 | :license: MIT, see LICENSE for more details. 7 | 8 | """ 9 | 10 | import random 11 | from collections import OrderedDict 12 | from typing import ( 13 | Any, 14 | Dict, 15 | Iterable, 16 | List, 17 | MutableMapping, 18 | Optional, 19 | Tuple, 20 | Union, 21 | cast, 22 | ) 23 | 24 | from pyvisa import constants, highlevel, rname 25 | from pyvisa.constants import StatusCode 26 | from pyvisa.typing import VISAEventContext, VISARMSession, VISASession 27 | from pyvisa.util import DebugInfo, LibraryPath 28 | 29 | from .common import LOGGER 30 | from .sessions import OpenError, Session 31 | 32 | 33 | class PyVisaLibrary(highlevel.VisaLibraryBase): 34 | """A pure Python backend for PyVISA. 35 | 36 | The object is basically a dispatcher with some common functions implemented. 37 | 38 | When a new resource object is requested to pyvisa, the library creates a 39 | Session object (that knows how to perform low-level communication operations) 40 | associated with a session handle (a number, usually refered just as session). 41 | 42 | A call to a library function is handled by PyVisaLibrary if it involves a 43 | resource agnostic function or dispatched to the correct session object 44 | (obtained from the session id). 45 | 46 | Importantly, the user is unaware of this. PyVisaLibrary behaves for 47 | the user just as NIVisaLibrary. 48 | 49 | """ 50 | 51 | #: Live session object identified by a randon session ID 52 | sessions: MutableMapping[ 53 | Union[VISASession, VISAEventContext, VISARMSession], Session 54 | ] 55 | 56 | # Try to import packages implementing lower level functionality. 57 | try: 58 | from .serial import SerialSession 59 | 60 | LOGGER.debug("SerialSession was correctly imported.") 61 | except Exception as e: 62 | LOGGER.debug("SerialSession was not imported %s." % e) 63 | 64 | try: 65 | from .usb import USBRawSession, USBSession 66 | 67 | LOGGER.debug("USBSession and USBRawSession were correctly imported.") 68 | except Exception as e: 69 | LOGGER.debug("USBSession and USBRawSession were not imported %s." % e) 70 | 71 | try: 72 | from .tcpip import TCPIPInstrSession, TCPIPSocketSession 73 | 74 | LOGGER.debug("TCPIPSession was correctly imported.") 75 | except Exception as e: 76 | LOGGER.debug("TCPIPSession was not imported %s." % e) 77 | 78 | try: 79 | from . import prologix 80 | 81 | if hasattr(prologix, "PrologixASRLIntfcSession"): 82 | ss = "PrologixASRLIntfcSession and PrologixTCPIPIntfcSession" 83 | else: 84 | ss = "PrologixTCPIPIntfcSession" 85 | 86 | LOGGER.debug(f"{ss} were correctly imported.") 87 | except Exception as e: 88 | LOGGER.debug( 89 | "PrologixASRLIntfcSession and PrologixTCPIPIntfcSession were not imported: %s." 90 | % e 91 | ) 92 | 93 | try: 94 | from .gpib import GPIBSession 95 | 96 | LOGGER.debug("GPIBSession was correctly imported.") 97 | except Exception as e: 98 | LOGGER.debug("GPIBSession was not imported %s." % e) 99 | 100 | @staticmethod 101 | def get_library_paths() -> Iterable[LibraryPath]: 102 | """List a dummy library path to allow to create the library.""" 103 | return (LibraryPath("py"),) 104 | 105 | @staticmethod 106 | def get_debug_info() -> DebugInfo: 107 | """Return a list of lines with backend info.""" 108 | from . import __version__ 109 | 110 | d: OrderedDict[str, Union[str, List[str], Dict[str, str]]] = OrderedDict() 111 | d["Version"] = "%s" % __version__ 112 | 113 | for key, val in Session.iter_valid_session_classes(): 114 | key_name = "%s %s" % (key[0].name.upper(), key[1]) 115 | d[key_name] = "Available " + val.get_low_level_info() 116 | 117 | for key, issue in Session.iter_session_classes_issues(): 118 | key_name = "%s %s" % (key[0].name.upper(), key[1]) 119 | d[key_name] = issue.split("\n") 120 | 121 | return d 122 | 123 | def _init(self) -> None: 124 | """Custom initialization code.""" 125 | # Map session handle to session object. 126 | self.sessions = {} 127 | 128 | def _register(self, obj: Session) -> VISASession: 129 | """Creates a random but unique session handle for a session object. 130 | 131 | Register it in the sessions dictionary and return the value. 132 | 133 | """ 134 | while True: 135 | session = VISASession(random.randint(1000000, 9999999)) 136 | if session not in self.sessions: 137 | break 138 | 139 | self.sessions[session] = obj 140 | return session 141 | 142 | def open( 143 | self, 144 | session: VISARMSession, 145 | resource_name: str, 146 | access_mode: constants.AccessModes = constants.AccessModes.no_lock, 147 | open_timeout: Optional[int] = constants.VI_TMO_IMMEDIATE, 148 | ) -> Tuple[VISASession, StatusCode]: 149 | """Opens a session to the specified resource. 150 | 151 | Corresponds to viOpen function of the VISA library. 152 | 153 | Parameters 154 | ---------- 155 | session : VISARMSession 156 | Resource Manager session (should always be a session returned from 157 | open_default_resource_manager()). 158 | resource_name : str 159 | Unique symbolic name of a resource. 160 | access_mode : constants.AccessModes, optional 161 | Specifies the mode by which the resource is to be accessed. 162 | open_timeout : int 163 | Specifies the maximum time period (in milliseconds) that this 164 | operation waits before returning an error. constants.VI_TMO_IMMEDIATE 165 | and constants.VI_TMO_INFINITE are used as min and max. 166 | 167 | Returns 168 | ------- 169 | VISASession 170 | Unique logical identifier reference to a session 171 | StatusCode 172 | Return value of the library call. 173 | 174 | """ 175 | try: 176 | open_timeout = None if open_timeout is None else int(open_timeout) 177 | except ValueError: 178 | raise ValueError( 179 | "open_timeout (%r) must be an integer (or compatible type)" 180 | % open_timeout 181 | ) 182 | 183 | try: 184 | parsed = rname.parse_resource_name(resource_name) 185 | except rname.InvalidResourceName: 186 | return ( 187 | VISASession(0), 188 | self.handle_return_value(None, StatusCode.error_invalid_resource_name), 189 | ) 190 | 191 | cls = Session.get_session_class( 192 | parsed.interface_type_const, parsed.resource_class 193 | ) 194 | 195 | try: 196 | sess = cls(session, resource_name, parsed, open_timeout) 197 | except OpenError as e: 198 | return VISASession(0), self.handle_return_value(None, e.error_code) 199 | 200 | return self._register(sess), StatusCode.success 201 | 202 | def clear(self, session: VISASession) -> StatusCode: 203 | """Clears a device. 204 | 205 | Corresponds to viClear function of the VISA library. 206 | 207 | Parameters 208 | ---------- 209 | session : typing.VISASession 210 | Unique logical identifier to a session. 211 | 212 | Returns 213 | ------- 214 | StatusCode 215 | Return value of the library call. 216 | 217 | """ 218 | try: 219 | sess = self.sessions[session] 220 | except KeyError: 221 | return self.handle_return_value(session, StatusCode.error_invalid_object) 222 | return self.handle_return_value(session, sess.clear()) 223 | 224 | def flush( 225 | self, session: VISASession, mask: constants.BufferOperation 226 | ) -> StatusCode: 227 | """Flush the specified buffers. 228 | 229 | The buffers can be associated with formatted I/O operations and/or 230 | serial communication. 231 | 232 | Corresponds to viFlush function of the VISA library. 233 | 234 | Parameters 235 | ---------- 236 | session : VISASession 237 | Unique logical identifier to a session. 238 | mask : constants.BufferOperation 239 | Specifies the action to be taken with flushing the buffer. 240 | The values can be combined using the | operator. However multiple 241 | operations on a single buffer cannot be combined. 242 | 243 | Returns 244 | ------- 245 | StatusCode 246 | Return value of the library call. 247 | 248 | """ 249 | try: 250 | sess = self.sessions[session] 251 | except KeyError: 252 | return self.handle_return_value(session, StatusCode.error_invalid_object) 253 | return self.handle_return_value(session, sess.flush(mask)) 254 | 255 | def gpib_command( 256 | self, session: VISASession, command_byte: bytes 257 | ) -> Tuple[int, StatusCode]: 258 | """Write GPIB command bytes on the bus. 259 | 260 | Corresponds to viGpibCommand function of the VISA library. 261 | 262 | Parameters 263 | ---------- 264 | session : VISASession 265 | Unique logical identifier to a session. 266 | command_byte : bytes 267 | Data to write. 268 | 269 | Returns 270 | ------- 271 | int 272 | Number of written bytes 273 | StatusCode 274 | Return value of the library call. 275 | 276 | """ 277 | try: 278 | written, st = self.sessions[session].gpib_command(command_byte) 279 | return written, self.handle_return_value(session, st) 280 | except KeyError: 281 | return 0, self.handle_return_value(session, StatusCode.error_invalid_object) 282 | 283 | def assert_trigger( 284 | self, session: VISASession, protocol: constants.TriggerProtocol 285 | ) -> StatusCode: 286 | """Assert software or hardware trigger. 287 | 288 | Corresponds to viAssertTrigger function of the VISA library. 289 | 290 | Parameters 291 | ---------- 292 | session : VISASession 293 | Unique logical identifier to a session. 294 | protocol : constants.TriggerProtocol 295 | Trigger protocol to use during assertion. 296 | 297 | Returns 298 | ------- 299 | StatusCode 300 | Return value of the library call. 301 | 302 | """ 303 | try: 304 | return self.handle_return_value( 305 | session, self.sessions[session].assert_trigger(protocol) 306 | ) 307 | except KeyError: 308 | return self.handle_return_value(session, StatusCode.error_invalid_object) 309 | 310 | def gpib_send_ifc(self, session: VISASession) -> StatusCode: 311 | """Pulse the interface clear line (IFC) for at least 100 microseconds. 312 | 313 | Corresponds to viGpibSendIFC function of the VISA library. 314 | 315 | Parameters 316 | ---------- 317 | session : VISASession 318 | Unique logical identifier to a session. 319 | 320 | Returns 321 | ------- 322 | StatusCode 323 | Return value of the library call. 324 | 325 | """ 326 | try: 327 | return self.handle_return_value( 328 | session, self.sessions[session].gpib_send_ifc() 329 | ) 330 | except KeyError: 331 | return self.handle_return_value(session, StatusCode.error_invalid_object) 332 | 333 | def gpib_control_ren( 334 | self, session: VISASession, mode: constants.RENLineOperation 335 | ) -> StatusCode: 336 | """Controls the state of the GPIB Remote Enable (REN) interface line. 337 | 338 | Optionally the remote/local state of the device can also be set. 339 | 340 | Corresponds to viGpibControlREN function of the VISA library. 341 | 342 | Parameters 343 | ---------- 344 | session : VISASession 345 | Unique logical identifier to a session. 346 | mode : constants.RENLineOperation 347 | State of the REN line and optionally the device remote/local state. 348 | 349 | Returns 350 | ------- 351 | StatusCode 352 | Return value of the library call. 353 | 354 | """ 355 | try: 356 | return self.handle_return_value( 357 | session, self.sessions[session].gpib_control_ren(mode) 358 | ) 359 | except KeyError: 360 | return self.handle_return_value(session, StatusCode.error_invalid_object) 361 | 362 | def gpib_control_atn( 363 | self, session: VISASession, mode: constants.ATNLineOperation 364 | ) -> StatusCode: 365 | """Specifies the state of the ATN line and the local active controller state. 366 | 367 | Corresponds to viGpibControlATN function of the VISA library. 368 | 369 | Parameters 370 | ---------- 371 | session : VISASession 372 | Unique logical identifier to a session. 373 | mode : constants.ATNLineOperation 374 | State of the ATN line and optionally the local active controller state. 375 | 376 | Returns 377 | ------- 378 | StatusCode 379 | Return value of the library call. 380 | 381 | """ 382 | try: 383 | return self.handle_return_value( 384 | session, self.sessions[session].gpib_control_atn(mode) 385 | ) 386 | except KeyError: 387 | return self.handle_return_value(session, StatusCode.error_invalid_object) 388 | 389 | def gpib_pass_control( 390 | self, session: VISASession, primary_address: int, secondary_address: int 391 | ) -> StatusCode: 392 | """Tell a GPIB device to become controller in charge (CIC). 393 | 394 | Corresponds to viGpibPassControl function of the VISA library. 395 | 396 | Parameters 397 | ---------- 398 | session : VISASession 399 | Unique logical identifier to a session. 400 | primary_address : int 401 | Primary address of the GPIB device to which you want to pass control. 402 | secondary_address : int 403 | Secondary address of the targeted GPIB device. 404 | If the targeted device does not have a secondary address, this parameter 405 | should contain the value Constants.VI_NO_SEC_ADDR. 406 | 407 | Returns 408 | ------- 409 | StatusCode 410 | Return value of the library call. 411 | 412 | """ 413 | try: 414 | return self.handle_return_value( 415 | session, 416 | self.sessions[session].gpib_pass_control( 417 | primary_address, secondary_address 418 | ), 419 | ) 420 | except KeyError: 421 | return self.handle_return_value(session, StatusCode.error_invalid_object) 422 | 423 | def read_stb(self, session: VISASession) -> Tuple[int, StatusCode]: 424 | """Reads a status byte of the service request. 425 | 426 | Corresponds to viReadSTB function of the VISA library. 427 | 428 | Parameters 429 | ---------- 430 | session : VISASession 431 | Unique logical identifier to a session. 432 | 433 | Returns 434 | ------- 435 | int 436 | Service request status byte 437 | StatusCode 438 | Return value of the library call. 439 | 440 | """ 441 | try: 442 | sess = self.sessions[session] 443 | except KeyError: 444 | return 0, self.handle_return_value(session, StatusCode.error_invalid_object) 445 | stb, status_code = sess.read_stb() 446 | return stb, self.handle_return_value(session, status_code) 447 | 448 | def close( 449 | self, session: Union[VISASession, VISAEventContext, VISARMSession] 450 | ) -> StatusCode: 451 | """Closes the specified session, event, or find list. 452 | 453 | Corresponds to viClose function of the VISA library. 454 | 455 | Parameters 456 | --------- 457 | session : Union[VISASession, VISAEventContext, VISARMSession] 458 | Unique logical identifier to a session, event, resource manager. 459 | 460 | Returns 461 | ------- 462 | StatusCode 463 | Return value of the library call. 464 | 465 | """ 466 | try: 467 | sess = self.sessions[session] 468 | # The RM session directly references the library. 469 | if sess is not self: 470 | return self.handle_return_value(session, sess.close()) 471 | else: 472 | return self.handle_return_value(session, StatusCode.success) 473 | except KeyError: 474 | return self.handle_return_value(session, StatusCode.error_invalid_object) 475 | 476 | def open_default_resource_manager(self) -> Tuple[VISARMSession, StatusCode]: 477 | """This function returns a session to the Default Resource Manager resource. 478 | 479 | Corresponds to viOpenDefaultRM function of the VISA library. 480 | 481 | Returns 482 | ------- 483 | VISARMSession 484 | Unique logical identifier to a Default Resource Manager session 485 | StatusCode 486 | Return value of the library call. 487 | 488 | """ 489 | return ( 490 | cast(VISARMSession, self._register(cast(Session, self))), 491 | self.handle_return_value(None, StatusCode.success), 492 | ) 493 | 494 | def list_resources( 495 | self, session: VISARMSession, query: str = "?*::INSTR" 496 | ) -> Tuple[str, ...]: 497 | """Return a tuple of all connected devices matching query. 498 | 499 | Parameters 500 | ---------- 501 | session : VISARMSession 502 | Unique logical identifier to the resource manager session. 503 | query : str 504 | Regular expression used to match devices. 505 | 506 | Returns 507 | ------- 508 | Tuple[str, ...] 509 | Resource names of all the connected devices matching the query. 510 | 511 | """ 512 | # For each session type, ask for the list of connected resources and 513 | # merge them into a single list. 514 | # HINT: the cast should not be necessary here 515 | resources: List[str] = [] 516 | for key, st in Session.iter_valid_session_classes(): 517 | resources += st.list_resources() 518 | 519 | return rname.filter(resources, query) 520 | 521 | def read(self, session: VISASession, count: int) -> Tuple[bytes, StatusCode]: 522 | """Reads data from device or interface synchronously. 523 | 524 | Corresponds to viRead function of the VISA library. 525 | 526 | Parameters 527 | ---------- 528 | session : VISASession 529 | Unique logical identifier to a session. 530 | count : int 531 | Number of bytes to be read. 532 | 533 | Returns 534 | ------- 535 | bytes 536 | Date read 537 | StatusCode 538 | Return value of the library call. 539 | 540 | """ 541 | # from the session handle, dispatch to the read method of the session object. 542 | try: 543 | data, status_code = self.sessions[session].read(count) 544 | except KeyError: 545 | return ( 546 | b"", 547 | self.handle_return_value(session, StatusCode.error_invalid_object), 548 | ) 549 | 550 | return data, self.handle_return_value(session, status_code) 551 | 552 | def write(self, session: VISASession, data: bytes) -> Tuple[int, StatusCode]: 553 | """Write data to device or interface synchronously. 554 | 555 | Corresponds to viWrite function of the VISA library. 556 | 557 | Parameters 558 | ---------- 559 | session : VISASession 560 | Unique logical identifier to a session. 561 | data : bytes 562 | Data to be written. 563 | 564 | Returns 565 | ------- 566 | int 567 | Number of bytes actually transferred 568 | StatusCode 569 | Return value of the library call. 570 | 571 | """ 572 | # from the session handle, dispatch to the write method of the session object. 573 | try: 574 | written, status_code = self.sessions[session].write(data) 575 | except KeyError: 576 | return 0, self.handle_return_value(session, StatusCode.error_invalid_object) 577 | 578 | return written, self.handle_return_value(session, status_code) 579 | 580 | def buffer_read(self, session: VISASession, count: int) -> Tuple[bytes, StatusCode]: 581 | """Reads data through the use of a formatted I/O read buffer. 582 | 583 | The data can be read from a device or an interface. 584 | 585 | Corresponds to viBufRead function of the VISA library. 586 | 587 | Parameters 588 | ---------- 589 | session : VISASession\ 590 | Unique logical identifier to a session. 591 | count : int 592 | Number of bytes to be read. 593 | 594 | Returns 595 | ------- 596 | bytes 597 | Data read 598 | StatusCode 599 | Return value of the library call. 600 | 601 | """ 602 | return self.read(session, count) 603 | 604 | def buffer_write(self, session: VISASession, data: bytes) -> Tuple[int, StatusCode]: 605 | """Writes data to a formatted I/O write buffer synchronously. 606 | 607 | Corresponds to viBufWrite function of the VISA library. 608 | 609 | Parameters 610 | ---------- 611 | session : VISASession 612 | Unique logical identifier to a session. 613 | data : bytes 614 | Data to be written. 615 | 616 | Returns 617 | ------- 618 | int 619 | number of written bytes 620 | StatusCode 621 | return value of the library call. 622 | 623 | """ 624 | return self.write(session, data) 625 | 626 | def get_attribute( 627 | self, 628 | session: Union[VISASession, VISAEventContext, VISARMSession], 629 | attribute: Union[constants.ResourceAttribute, constants.EventAttribute], 630 | ) -> Tuple[Any, StatusCode]: 631 | """Retrieves the state of an attribute. 632 | 633 | Corresponds to viGetAttribute function of the VISA library. 634 | 635 | Parameters 636 | ---------- 637 | session : Union[VISASession, VISAEventContext, VISARMSession] 638 | Unique logical identifier to a session, event, or find list. 639 | attribute : Union[constants.ResourceAttribute, constants.EventAttribute] 640 | Resource or event attribute for which the state query is made. 641 | 642 | Returns 643 | ------- 644 | Any 645 | State of the queried attribute for a specified resource 646 | StatusCode 647 | Return value of the library call. 648 | 649 | """ 650 | try: 651 | sess = self.sessions[session] 652 | except KeyError: 653 | return ( 654 | None, 655 | self.handle_return_value(session, StatusCode.error_invalid_object), 656 | ) 657 | 658 | state, status_code = sess.get_attribute( 659 | cast(constants.ResourceAttribute, attribute) 660 | ) 661 | return state, self.handle_return_value(session, status_code) 662 | 663 | def set_attribute( 664 | self, 665 | session: VISASession, 666 | attribute: constants.ResourceAttribute, 667 | attribute_state: Any, 668 | ) -> StatusCode: 669 | """Set the state of an attribute. 670 | 671 | Corresponds to viSetAttribute function of the VISA library. 672 | 673 | Parameters 674 | ---------- 675 | session : VISASession 676 | Unique logical identifier to a session. 677 | attribute : constants.ResourceAttribute 678 | Attribute for which the state is to be modified. 679 | attribute_state : Any 680 | The state of the attribute to be set for the specified object. 681 | 682 | Returns 683 | ------- 684 | StatusCode 685 | Return value of the library call. 686 | 687 | """ 688 | try: 689 | return self.handle_return_value( 690 | session, 691 | self.sessions[session].set_attribute(attribute, attribute_state), 692 | ) 693 | except KeyError: 694 | return self.handle_return_value(session, StatusCode.error_invalid_object) 695 | 696 | def lock( 697 | self, 698 | session: VISASession, 699 | lock_type: constants.Lock, 700 | timeout: int, 701 | requested_key: Optional[str] = None, 702 | ) -> Tuple[str, StatusCode]: 703 | """Establishes an access mode to the specified resources. 704 | 705 | Corresponds to viLock function of the VISA library. 706 | 707 | Parameters 708 | ---------- 709 | session : VISASession 710 | Unique logical identifier to a session. 711 | lock_type : constants.Lock 712 | Specifies the type of lock requested. 713 | timeout : int 714 | Absolute time period (in milliseconds) that a resource waits to get 715 | unlocked by the locking session before returning an error. 716 | requested_key : Optional[str], optional 717 | Requested locking key in the case of a shared lock. For an exclusive 718 | lock it should be None. 719 | 720 | Returns 721 | ------- 722 | str 723 | Key that can then be passed to other sessions to share the lock, or 724 | None for an exclusive lock. 725 | StatusCode 726 | Return value of the library call. 727 | 728 | """ 729 | try: 730 | sess = self.sessions[session] 731 | except KeyError: 732 | return ( 733 | "", 734 | self.handle_return_value(session, StatusCode.error_invalid_object), 735 | ) 736 | key, status_code = sess.lock(lock_type, timeout, requested_key) 737 | return key, self.handle_return_value(session, status_code) 738 | 739 | def unlock(self, session: VISASession) -> StatusCode: 740 | """Relinquish a lock for the specified resource. 741 | 742 | Corresponds to viUnlock function of the VISA library. 743 | 744 | Parameters 745 | ---------- 746 | session : VISASession 747 | Unique logical identifier to a session. 748 | 749 | Returns 750 | ------- 751 | StatusCode 752 | Return value of the library call. 753 | 754 | """ 755 | try: 756 | sess = self.sessions[session] 757 | except KeyError: 758 | return self.handle_return_value(session, StatusCode.error_invalid_object) 759 | return self.handle_return_value(session, sess.unlock()) 760 | 761 | def disable_event( 762 | self, 763 | session: VISASession, 764 | event_type: constants.EventType, 765 | mechanism: constants.EventMechanism, 766 | ) -> StatusCode: 767 | """Disable notification for an event type(s) via the specified mechanism(s). 768 | 769 | Corresponds to viDisableEvent function of the VISA library. 770 | 771 | Parameters 772 | ---------- 773 | session : VISASession 774 | Unique logical identifier to a session. 775 | event_type : constants.EventType 776 | Event type. 777 | mechanism : constants.EventMechanism 778 | Event handling mechanisms to be disabled. 779 | 780 | Returns 781 | ------- 782 | StatusCode 783 | Return value of the library call. 784 | 785 | """ 786 | return StatusCode.error_nonimplemented_operation 787 | 788 | def discard_events( 789 | self, 790 | session: VISASession, 791 | event_type: constants.EventType, 792 | mechanism: constants.EventMechanism, 793 | ) -> StatusCode: 794 | """Discard event occurrences for a given type and mechanisms in a session. 795 | 796 | Corresponds to viDiscardEvents function of the VISA library. 797 | 798 | Parameters 799 | ---------- 800 | session : VISASession 801 | Unique logical identifier to a session. 802 | event_type : constans.EventType 803 | Logical event identifier. 804 | mechanism : constants.EventMechanism 805 | Specifies event handling mechanisms to be discarded. 806 | 807 | Returns 808 | ------- 809 | StatusCode 810 | Return value of the library call. 811 | 812 | """ 813 | return StatusCode.error_nonimplemented_operation 814 | --------------------------------------------------------------------------------